线段树
线段树总结
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
线段树的功能比以往的rmq、lca算法强大,不仅可以快速计算一个区间内的和,而且可以计算一个区间内最大(小)值,包含了rmq的功能和胜者树的功能,而且还可以做任意修改。
一、 线段树的构建。
用深搜构建线段树。
如上图所示,就是一棵线段树的构建。线段树上面的数字,如1,10,代表1~10号区间,依此类推。
深搜首先要确定停止条件,当左端点等于右端点如最后的叶子结点,就应该返回,同时赋值。
然后继续深搜下去,先序遍历,先查left child。
在查完以后,根结点的sum不要忘记加上左右结点的值。
同样的,如果是求最大(小)值,就取max(min),其它相同。
int n,a[100005],s,t,q; struct t { long long sum; }tree[400005]; void buildtree(int root,int l,int r) { if(l==r) { tree[root].sum=a[l]; printf("%d ",tree[root].sum); return; } buildtree(root*2,l,(l+r)/2); buildtree(root*2+1,(l+r)/2+1,r); tree[root].sum=tree[root*2].sum+tree[root*2+1].sum; printf("%d ",tree[root].sum); } int main() { freopen("1660.in","r",stdin); freopen("1660.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); buildtree(1,1,n); return 0; }
二.线段树查询区间。
上面已经说了线段树的构建,可是如果要查询一个区间中的和或者最大(小)值要怎么样得到呢。
假设要求最小值。首先,我们要判断当前的区间与要求的区间s,t是否相关,判断if(s>r||t<l) return oo;如果无关,就返回一个无穷大,答案就不会受到影响。 一开始的时候,我还是按照求和的写法,直接返回0,最小值肯定是0,所以就不对了。
然后判断当前的区间是否被包含在要求的那个区间之内,如果包含,那么便直接返回那个区间的最小值就可以了。f(s<=l&&r<=t) return tree[root].mi;
为了方便下面求leftchild和rightchild,先开一个临时变量mid=(L+R)/2; 然后把leftchild和rightchild区间的最小值再min一下就可以得出最后的结果了。
同样的,最大值也是这样求。
int n,a[100005],s,t,q,an,ss,oo=1000000; struct t { int ma; int mi; }tree[400005]; void buildtree(int root,int l,int r) { if(l==r) { tree[root].ma=a[l]; tree[root].mi=a[l]; return; } buildtree(root*2,l,(l+r)/2); buildtree(root*2+1,(l+r)/2+1,r); tree[root].ma=max(tree[root*2].ma,tree[root*2+1].ma); tree[root].mi=min(tree[root*2].mi,tree[root*2+1].mi); } int get1(int root,int l,int r,int s,int t) { int ans1=0,ans2=0 ,ans=0; if(s<=l&&r<=t) return tree[root].ma; if(s>r||t<l) return -oo; int mid=(l+r)/2; ans1=get1(root*2,l,mid,s,t); ans2=get1(root*2+1,mid+1,r,s,t); ans=max(ans1,ans2); return ans; } int get(int root,int l,int r,int s,int t) { int ans1=0,ans2=0,ans=0; if(s<=l&&r<=t) return tree[root].mi; if(s>r||t<l) return oo; int mid=(l+r)/2; ans1=get(root*2,l,mid,s,t); ans2=get(root*2+1,mid+1,r,s,t); ans=min(ans1,ans2); return ans; } int main() { freopen("1656.in","r",stdin); freopen("1656.out","w",stdout); scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) scanf("%d",&a[i]); buildtree(1,1,n); while(q) { q--; scanf("%d%d",&s,&t); if(s>t) swap(s,t); an=get1(1,1,n,s,t); ss=get(1,1,n,s,t); int ans=an-ss; printf("%d",ans); } return 0; }
三、 修改一个数字
当L==x并且R==x时把tree[root].ma修改成y。然后再改变两个孩子,但是改变完以后,要记得重新max一下tree[root]的值,保证正确性。
void change(int root,int l,int r,int x,int y)//改动 { if(l>x||x>r) return; if(l==x&&r==x) { tree[root].ma=y; return; } int mid=(l+r)/2; change(root*2,l,mid,x,y); change(root*2+1,mid+1,r,x,y); tree[root].ma=max(tree[root*2].ma,tree[root*2+1].ma); }
三、 线段树修改一段的值
这个相对起前面的就复杂很多了。首先,如果要改一段,一定会影响到很多。但如果逐个去改就好比for循环,要用很长的时间。
我们可以把要加减的值先记录下来,把tree[root].sum加(减)上,不作其他处理。如果当前的区间完全包含在给出的区间里面时,便把当前这个root的记录增加值add加上,然后return,这个和前面是一样的,不相干时也return。
如果不符合上面的条件,就把根结点的add分散给孩子,首先,把左右孩子的add+=根结点的add,然后,左右孩子的sum要由长度(R-L+1)乘add。最后,再把根结点的add清零。
然后,同样是轮到左孩子和右孩子。其他基本不变。
但是,如果是求最大(小)要注意了,不能sum=(R-L+1)*add这样求的是sum值,直接加上add就可以了。
取sum:
struct t { long long add; long long sum; }tree[400005]; void buildtree(int root,int l,int r)//建树 { if(l==r) { tree[root].sum=a[l]; return; } int mid=(l+r)/2; buildtree(root*2,l,mid); buildtree(root*2+1,mid+1,r); tree[root].sum=tree[root*2].sum+tree[root*2+1].sum; } void down(int root,int l,int r)//分散 { tree[root*2].add+=tree[root].add; tree[root*2+1].add+=tree[root].add; int mid=(l+r)/2; tree[root*2].sum+=(mid-l+1)*tree[root].add; tree[root*2+1].sum+=(r-mid)*tree[root].add; tree[root].add=0; } void update(int root,int l,int r,int s,int t,long long v)//改动 { if(s>r||t<l) return; if(s<=l&&t>=r) { tree[root].sum+=(r-l+1)*v; return; } down(root,l,r); int mid=(l+r)/2; update(root*2,l,mid,s,t,v); update(root*2+1,mid+1,r,s,t,v); tree[root].sum=tree[root*2].sum+tree[root*2+1].sum; } long long get(int root,int l,int r,int s,int t)//得出答案 { long long ans=0; if(s>r||t<l) return 0; if(s<=l&&t>=r) { return tree[root].sum; } down(root,l,r); int mid=(l+r)/2; ans+=get(root*2,l,mid,s,t); ans+=get(root*2+1,mid+1,r,s,t); return ans; }
取max:
struct t { int add; int sum; }tree[400005]; void buildtree(int root,int l,int r) { if(l==r) { tree[root].sum=a[l]; return; } int mid=(l+r)/2; buildtree(root*2,l,mid); buildtree(root*2+1,mid+1,r); tree[root].sum=max(tree[root*2].sum,tree[root*2+1].sum); } void down(int root,int l,int r) { tree[root*2].add+=tree[root].add; tree[root*2+1].add+=tree[root].add; int mid=(l+r)/2; tree[root*2].sum+=tree[root].add; tree[root*2+1].sum+=tree[root].add; tree[root].add=0; } void update(int root,int l,int r,int s,int t,int v) { if(s>r||t<l) return; if(s<=l&&t>=r) { tree[root].add+=v; tree[root].sum+=v; return; } down(root,l,r); int mid=(l+r)/2; update(root*2,l,mid,s,t,v); update(root*2+1,mid+1,r,s,t,v); tree[root].sum=max(tree[root*2].sum,tree[root*2+1].sum); } int get(int root,int l,int r,int s,int t) { int ans=-oo; if(s>r||t<l) return -oo; if(s<=l&&t>=r) { return tree[root].sum; } down(root,l,r); int mid=(l+r)/2; ans=max(get(root*2+1,mid+1,r,s,t),get(root*2,l,mid,s,t)); return ans; }