线段树是一种二叉搜索树,其存储的是一个区间的信息,用[L,R]表示从L到R之间存在的信息。
在结构体中再定义一个对象 lazy,add记录的是此节点所代表的区间所有数被加的值,在我们进行操作时,比如加减,我们就可以利用lazy然后往下修改,而不用修改一次就遍历一次,大大节约时间。
建树、单点查询、单点修改、区间查询、区间修改
结点的构建:
const int maxn=1e6; struct node{ int l, r,lazy,val; }tree[maxn<<2];//线段树要开数组的4倍
以求和为例
void build(int l,int r,int rt){ tree[rt].lazy=0;//初始化lazy标记 tree[rt].l=l; tree[rt].r=r; if(l==r){ tree[rt].val=a[l]; return; } else{ int mid=(l+r)>>1; build(l,mid,rt*2);//左孩子 build(mid,r,rt*2+1);//右孩子 tree[rt].val=tree[rt*2].val+tree[rt*2+1].val;状态合并,此结点的为两个孩子的val和 } }
int querynode(int rt){//单点查询(二分) if(tree[rt].l==tree[rt].r){//当前结点的左右端点相等,为叶子节点,是最终答案 return tree[rt].val; } int mid=(tree[rt].l+tree[rt].r)>>1; if(rt<=mid)//目标位置比中点靠左,就递归左孩子 querynode(rt*2); else//反之,递归右孩子 querynode(rt*2+1); }
void updatenode(int rt,int c){ if(tree[rt].l==tree[rt].r){//找到目标位置 tree[rt].val+=c; return; } int mid=(tree[rt].l+tree[rt].r)>>1; if(rt<=mid)//目标位置比中点靠左,就递归左孩子 updatenode(rt*2,c); else//反之,递归右孩子 updatenode(rt*2+1,c); tree[rt].val=tree[rt*2].val+tree[rt*2+1].val;//所有包含结点k的结点状态更新 }
int query(int l,int r,int rt){//区间查询 if(tree[rt].l>=l&&tree[rt].r<=r) return tree[rt].val; else if(l>l||r<1) return 0; else{ return query(l,r,rt<<1)+query(l,r,rt<<1|1); } }
为了避免超时,引用延迟标记
每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新 or 询问的时候。下次更新或者查询的时候,如果查到该节点,就把延迟标记进行下传,将值加到他的子节点上去,同时将延迟标记变为 0,避免下次重复更新。这样只更新到查询的子区间,不需要再往下找了,极大的降低了时间复杂度。
下推:
void pushdown(int rt){//下推 tree[rt*2].lazy+=tree[rt].lazy;//左孩子更新延迟标记 tree[rt*2+1].lazy+=tree[rt].lazy;//右孩子更新延迟标记 tree[rt*2].val+=tree[rt].lazy*(tree[rt*2].r-tree[rt*2].l+1);//左孩子状态更新 tree[rt*2+1].val+=tree[rt].lazy*(tree[rt*2+1].r-tree[rt*2+1].l+1);//右孩子状态更新 tree[rt].lazy=0;//清除本结点标记 }
区间修改:
void updata(int l,int r,int rt,int c){//区间更新 if(tree[rt].l>=l&&tree[rt].r<=r){ tree[rt].val+=c*(tree[rt].r-tree[rt].l+1); tree[rt].lazy+=c; return; } pushdown(rt); updata(l,r,rt*2,c); updata(l,r,rt*2+1,c); tree[rt].val=tree[rt*2].val+tree[rt*2+1].val; }
明天整理题目