1.线段树是什么?
基于分治思想的二叉树结构,用于区间操作。
2.主要操作(例子是:线段树维护区间最大值)
- 建树
一点点分着向下拓展,左儿子编号是父节点编号*2,右儿子编号是左儿子编号+1;
void create(int fa,int l,int r,long long t[]) { stree[fa].lazy=0; if(l==r) stree[fa].val=t[l]; else { int mid=(l+r)/2; create(2*fa,l,mid,t); create(2*fa+1,mid+1,r,t); stree[fa].val=stree[2*fa].val+stree[2*fa+1].val; } }
- 单点修改
像建树时的一点点拓展一样,我们需要一直二分找到该点,并且把这个点父节点和祖父节点的所有区间都更新
void change (int fa,int x,int v) { if(stree[fa].l==stree[fa].r) { stree[fa].val=v; return; } int mid=(stree[fa].l+stree[fa].r)>>1; if(x<=mid) change(fa*2,x,v); else change(fa*2+1,x,v); stree[fa].val=max(stree[fa*2].val,stree[fa*2+1].val); }
- 区间查询
查询 区间【l,r】的最大值;
1.若【l,r】完全覆盖了当前节点代表的区间,立即回溯,并且该节点的val为候选
2.若左端点与【l,r】有重叠部分,就递归进入左儿子
3.同理,若右端点与【l,r】有重叠部分,就递归进入右儿子
int askaskask(int fa,int l,int r) { if(l<=stree[fa].l&&r>=stree[fa].r) return stree[fa].val; int mid=(stree[fa].l+stree[fa].r)>>1; int ans=-0x3f3f3f3f if(l<=mid) ans=max(ans,askaskask(fa*2,l,r)); else ans=max(ans,askaskask(fa*2+1,l,r)); return ans; }
- 区间加和区间乘(需要延迟标记和下放操作)
如果我们进行区间加的时候,区间内每一个都加,还是要递归进行,每个子节点都会被更新到,那么复杂度就从o(logn)上升到o(n)了,我们肯定不想这种情况出现,所以我们有了
延迟标记(只在要修改的区间标记上我们干啥了,先不像单点修改那样更新父节点),顾名思义,延迟标记的作用是让我们只有在用的时候才把它下放,标记的时候是o(1),下放的时候是o(logn)的,保证了插入的时间复杂度;
那么问题来了,我们怎么下放???
直接看代码,简单的只有加和只有乘你们通过这个既有加又有乘的例子自行体会。
void shutdown(int root,int l,int r)//下放 { if(point[root].mull!=1)//先下放乘法标记,再下放加法标记; { point[2*root].addl=(point[root].mull*point[2*root].addl)%p; point[2*root].mull=(point[root].mull*point[2*root].mull)%p; point[2*root+1].addl=(point[root].mull*point[2*root+1].addl)%p; point[2*root+1].mull=(point[root].mull*point[2*root+1].mull)%p; point[2*root].value=(point[2*root].value*point[root].mull)%p; point[2*root+1].value=(point[2*root+1].value*point[root].mull)%p; point[root].mull=1; //下放完记得清空标记; } int mid=(l+r)>>1; point[2*root].addl+=point[root].addl; point[2*root].value+=point[root].addl*(mid-l+1); point[2*root+1].addl+=point[root].addl; point[2*root+1].value+=point[root].addl*(r-mid); point[root].addl=0; } void add(int root,int l,int r,int ll,int rr,int plus)//区间加 { if(l>rr||r<ll) return ; else if(l>=ll&&r<=rr) { point[root].addl+=plus; point[root].value+=plus*(r-l+1); return ; } else { int mid=(l+r)>>1; shutdown(root,l,r); add(2*root,l,mid,ll,rr,plus); add(2*root+1,mid+1,r,ll,rr,plus); point[root].value=point[2*root].value+point[2*root+1].value; } } void mul(int root,int l,int r,int ll,int rr,int multiply)//区间乘 { if(l>rr||r<ll) return ; else if(l>=ll&&r<=rr) { point[root].addl=(point[root].addl*multiply)%p; point[root].mull=(point[root].mull*multiply)%p; point[root].value=(point[root].value*multiply)%p; return ; } else { int mid=(l+r)>>1; shutdown(root,l,r); mul(2*root,l,mid,ll,rr,multiply); mul(2*root+1,mid+1,r,ll,rr,multiply); point[root].value=point[2*root].value+point[2*root+1].value; } }
3.注意事项
如果是一个有N个叶子节点的二叉树,共有2n-1个节点,又因为我们用父子二倍的节点编号,所以保存线段树的数组长度不小于4N才能保证不越界;
可以做两道模板题玩一玩:洛谷p3372 p3373
还有一道我觉得很神,巨佬们觉得很水的题:洛谷p4198 楼房重建。(其实考的就是思路)