zkw线段树小结

本文介绍了如何利用循环式线段树进行高效的单点修改和区间操作,特别是通过标记简化和差分技巧来优化区间求和、区间最小值等问题。讲解了如何预处理、更新和查询区间值,并提到了区间加减的技巧和核心思想,使得复杂标记处理更简洁高效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

z k w zkw zkw线段树作为循环式线段树具有较小的常数.(其实树状数组本质上就是线段树…

初始化

下标为 [ 1 , n ] [1,n] [1,n],预处理一个 2 k > n 2^k>n 2k>n.

然后总空间为 2 k + 1 2^{k+1} 2k+1.( 2 k + 1 < 4 n 2^{k+1}<4n 2k+1<4n)

后面令 k = 2 k k=2^k k=2k.

那么一个叶子 x x x的位置在 x + k x+k x+k.

单点修改

for(x += k; x;x /= 2) ....

直接遍历到根

区间修改

因为 z k w zkw zkw线段树是自底往上求,所以一般使用标记永久化 t r i c k trick trick.
所以如果维护的标记比较繁杂,还是打普通线段树比较好.

当遇到区间求和和区间修改的时候,我们令 l + = k − 1 , r + = k + 1 l+=k-1,r+=k+1 l+=k1,r+=k+1,即变为开区间.(这个是为了和的上传

当只有区间求和和单点修改的时候,我们可以考虑转化为左开右闭区间.

一个区间加是这样的:

void add(int l,int r,ll x) {
    int L=0,R=0,len=1;
    for(l += k-1, r += k+1;l^r^1;l/=2,r/=2,len*=2) {//l^r^1表示l,r不为兄弟节点
        if(~l & 1) ad[l^1] += x, s[l^1] += len*x, L += len;
        if( r & 1) ad[r^1] += x, s[r^1] += len*x, R += len;
        s[l>>1] += L*x;
        s[r>>1] += R*x; 
    }
    for(L += R;l/=2; s[l] += L*x); 
}

对应的区间求和:

ll sum(int l,int r) {
    ll ans=0; 
    int L=0,R=0,len=1;
    for(l += k-1, r += k+1;l^r^1;l/=2,r/=2,len*=2) {
        if(~l & 1) ans += s[l^1], L += len;
        if( r & 1) ans += s[r^1], R += len;
        ans += ad[l>>1]*L+ad[r>>1]*R;
    }
    for(L+=R;l/=2;ans+=ad[l]*L);
    return ans;
}

区间 min ⁡ , max ⁡ \min,\max min,max

这个在没有修改的情况下还是很好求的,只要预处理一下就好.

但是如果遇上区间标记呢?(比如区间加

z k w zkw zkw P P T PPT PPT内有讲到一个核心思想:

标记和值都是相对的数,那么我们何必同时维护标记和值呢?

为了同化,我们直接差分,这样取原值我们直接遍历到根求和即可.

具体地,比方说我们要维护区间 min ⁡ \min min:

那么我们令 d m n [ i ] = m n [ i ] − m n [ i / 2 ] dmn[i]=mn[i]-mn[i/2] dmn[i]=mn[i]mn[i/2], m n [ i ] mn[i] mn[i]表示区间最小值,代码内实际上只保留 d m n dmn dmn.

预处理:

for(int i=1;i<=n;i++) mn[i+k]=a[i];
for(int i=k-1; i;i--) {
    int A=min(mn[i*2],mn[i*2|1]);
    mn[i] += A; mn[i*2] -= A; mn[i*2+1] -= A;
}

对应的区间加应该是这样的:

void upd(int x) {
    int A=min(mn[x*2],mn[x*2|1]);
    mn[x] += A; mn[x*2] -= A; mn[x*2+1] -= A;
}
void add(int l,int r,ll x) {
    for(l += k-1,r += k+1;l^r^1;l/=2,r/=2) {
        if(~l & 1) mn[l^1] += x;
        if( r & 1) mn[r^1] += x;
        upd(l/2); upd(r/2);
    }
    for( ;l/=2;upd(l));
}

min ⁡ \min min:

int Min(int l,int r) {
    if(l == r) return ask(l);//特判,否则会死循环
    int L=0,R=0;
    for(l += k, r += k; l^r^1;l=l/2,r=r/2) {
        L += mn[l]; R += mn[r];
        if(~l&1) cmin(L,mn[l^1]);
        if( r&1) cmin(R,mn[r^1]);
    }
    int ans=min(L+mn[l],R+mn[r]);
    for( ;l /= 2; ) ans += mn[l];
    return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值