线段树

————————————18.4.18更新

有时我们遇到一些非常恶心神犇的题:数据量又大,又要做在线修改在线返值的题,显然这时我们不可能用O(n)的数组一个一个地去修改,去求值。

这时,我们就需要用到线段树了!!!!!!

让我们来看看吧

线段树的简介

我们设置一个二叉树,并定义根节点的区间范围是(1,n)。

我们设一个点的区间是(l,r),则每个点的左儿子的区间范围是(l,(l+r)/2),右儿子的区间范围是((l+r)/2+1,r)。同时,它的左儿子的编号是它的编号的2倍,右儿子是编号2倍加一。


这样,我们就得到了一棵线段树了。。。请注意,它是一个严格的二叉平衡树

每个节点的都存储它表示区间的各种信息,如:该区间的最值,所有点的和,乘积。。。。而一个节点的值我们可以根据它的两个儿子的值求出来。

接下来的操作,如:求和,求最值,求乘积,修改区间等就可以在树上操作了,具体实现会在后面讲到

线段树的优劣

首先,线段树树的修改和查询都是o(log2n)的。所以说相比之前的暴力这就很coooooool了。

线段树的空间复杂度是o(n)的,但是实际上空间是n*8的,所以相比某些算法要差劲些。(例如,离线时差分数组(而且差分数组的空间仅为o(n))求和更厉害,倍增法求最大更厉害)

但是线段树是在线的,这就是它的优越性。树状数组也是在线的,常数比它小,但是线段树可以求最值,所以是很优秀的!!!!

线段树的代码实现

建树:在开始计算时,我们的线段树还没建起,所以需要初始化一下。

struct node {  
    LL sum,l,r,lazy,maxx; //maxx表示该区间的最大值,sum表示该区间的值,l,r表示该区间的左右界,lazy表示懒值 
} a[maxn];  
void btree(LL l,LL r,LL x) {  //l,r表示目前点的左右界,x表示目前节点的编号 
    if(l==r) {  //若该点的左右界相同了,则不往下继续建树了 
        a[x].l=l;  
        a[x].r=r;
	a[x].sum=s[l];  //该点的和就是它的初始值 
	a[x].maxx=a[x].sum;//该点的最值就是它的初始值 
        return;  
    }  
    LL p=x<<1;  
    a[x].l=l;  
    a[x].r=r;  
    btree(l,(l+r)>>1,p);   //建立左儿子,其编号为2*x 
    btree(1+((l+r)>>1),r,p+1);  //建立右儿子,其编号为2*x+1 
	a[x].sum=a[p].sum+a[p+1].sum;//它的区间值等于左儿子区间值加右儿子区间值 
	a[x].maxx=max(a[p].maxx,a[p+1].maxx);  //它的最值等于左儿子最值与右儿子最值的最值 
}

修改:我们要修改某个区间的值,如将某区间的所有点加某值。

如果暴力修改所有被包含区间后,我们发现在数据很大时会超时,这是为什么呢?因为修改某个区间我们发现要逐个修改该区间的所有树点,这样就无法发挥线段树的作用。这时其实我们先将修改时遇到的点权先更新一下,保证这个点的祖先们的值能得到更新,再给它的左右儿子上累计博主曾经因为这个改了一天的代码懒值,当我们询问到有懒值的点时,我们就下方懒值即可。(注:该懒值思想在后面用得非常多,请一定深刻理解)

void put(LL x){//下放操作 
	a[x].sum+=a[x].lazy*(a[x].r-a[x].l+1);//该点的和就是该点值加上它的区间长度与懒值的乘积 
	a[x].maxx+=a[x].lazy;//该区间的最大值就是它的最大值加上懒值 
	a[x<<1].lazy+=a[x].lazy;//累计左儿子的懒值 
	a[(x<<1)+1].lazy+=a[x].lazy;//累计右儿子的懒值 
	a[x].lazy=0;//该点下放完成 ,懒值为0 
}  
void modify(LL x,LL l,LL r,LL q) {//q表示区间全部点加上q  
    if(a[x].r<l||r<a[x].l)return;//如果询问到的区间左右区间超出的,就不再修改。 
	if(a[x].lazy)put(x);  //遇到有懒值就下放(注:此处可不下放,博主为了保险起见就修改了) 
    if(l<=a[x].l&&a[x].r<=r) {//如询问区间被修改区间完全被包含 ,则开始修改 
        a[x].lazy+=q;//加上懒值 
		put(x);  //下方更新该点 
        return ;  
    }  
    LL p=x<<1;  
    modify(p,l,r);  //更新左儿子 
    modify(p+1,l,r);  //更新右儿子 
    a[x].sum=a[p].sum+a[p+1].sum;  
    a[x].maxx=max(a[p].maxx,a[p+1].maxx);
}

查询:查询某个区间的和(最大值请自行思考博主懒得写了

LL getsum(LL x,LL l,LL r) {//查询区间和  
    if(a[x].r<l||r<a[x].l)return  0; //如果该区间超出查询区间时,则返回0 
    if(a[x].lazy)put(x);// 遇到有懒值就下放
    if(l<=a[x].l&&a[x].r<=r) {  
        return a[x].sum;  // 如询问区间被修改区间完全被包含 ,则返回该区间的和 
    }  
    LL p=x<<1,sl=getsum(p,l,r),sr=getsum(p+1,l,r);  //求左右所有儿子的和值 
    return sr+sl;  //返回该点的左右儿子区间和的值 
}

总结线段树:

非常优秀。。。在其他算法中也及其有用,如树状剖分等算法。。。。

博主打字很辛苦,希望各位神犇能够多多光顾该博。。谢谢



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值