线段树
线段树总结
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
线段树的功能比以往的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;
}

浙公网安备 33010602011771号