P5889 题解

2023-09-07 15:43:38 solution

一道挺好的线段树题,确实不好想到。

因为是一棵满二叉树,一个节点 \(s\) 的左儿子编号就是 \(2s\),右儿子就是 \(2s+1\),父亲就是 \(\lfloor\dfrac{s}{2}\rfloor\),题目中也给了提示。

那我们考虑如何合并区间信息呢?如果出现很多次跳到父亲的指令,那么之前的跳儿子指令都会被覆盖,所以我们记录一下最高跳到第几代祖宗,然后剩下的部分记跳到最高之后,向下最多跳到多少儿子,因为儿子分成左右,而每次都是按照 \(2s\) 或者 \(2s+1\) 来,那么跳到最高之后,儿子的部分应该为一段 \(01\) 串,所以我们用一个值维护这个 \(01\) 串即可。最后我们假设我们从 \(st\) 出发,得到的区间操作信息中最高要跳到 \(fa\) 级祖先,跳到祖先后可以到达 \(de\) 级儿子,儿子的 \(01\) 序列为 \(val\),那么答案即为 \((\lfloor\dfrac{s}{2^{fa}}\rfloor\times 2^{de})+val\)

我们发现这个 \(01\) 串可能很长甚至远远超过 long long 的范围怎么办?没关系啊,题目保证了跳跃一定有儿子,所以虽然我们在部分线段树节点维护的不是正确值,但查询的时候我们查到的肯定都是偏小的 \(01\) 串,此时将其合并后值不会大于 \(2^n-1\),也就是大区间的区间信息虽然假了,但是不会被我们用到。

而题目又让我们判断如果是根就无视操作 \(3\) 怎么办?那很简单,如果最高级祖先已经在 \(1\) 之上了,那我们在右移的时候会归 \(0\),此时无论 \(fa\) 多大都是 \(0\),然后做完以后把它变成 \(1\) 就满足条件了,所以我们将上式改为 \((\max(1,\lfloor\dfrac{s}{2^{fa}}\rfloor)\times 2^{de})+val\) 即可。

区间信息合并有点小麻烦,可以结合代码理解一下。

\(Code\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mid (l+r>>1)
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
int read(){
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c^48);
    return x*f;
}
struct segment{
    int fa,de;ll v;
    segment(){fa=de=v=0;}
}tr[2000006];
inline segment operator +(const segment &l,const segment &r){
    segment ans;
    if(l.de>r.fa){//如果左边下降的比右边上升的大
        ans.fa=l.fa;//左边就是最高
        ans.de=l.de-r.fa+r.de;//左边下降的顶掉了右边上升的
        ans.v=((l.v>>r.fa)<<r.de)+r.v;//左边的值保留一部分,用右边的更新
    }else{
        ans.fa=l.fa-l.de+r.fa;//高度增加了
        ans.de=r.de;//剩下的就是右边的答案,左边的答案被顶掉了
        ans.v=r.v;
    }
    return ans;
}
void build(int root,int l,int r){
    if(l==r){
        int w=read();//这里赋值写得应该比较清晰
        if(w==1)tr[root].de=1;
        else if(w==2)tr[root].de=1,tr[root].v=1;
        else tr[root].fa=1;
        return;
    }
    build(lson),build(rson);
    tr[root]=tr[root<<1]+tr[root<<1|1];//合并
}
void update(int root,int l,int r,int x,int w){
    if(l==r)return tr[root].de=(w<=2),tr[root].fa=(w==3),tr[root].v=(w==2),void();
    if(mid>=x)update(lson,x,w);
    else update(rson,x,w);
    tr[root]=tr[root<<1]+tr[root<<1|1];//合并
}
segment query(int root,int l,int r,int x,int y){
    if(x<=l&&r<=y)return tr[root];
    segment res;
    if(mid>=x)res=res+query(lson,x,y);//把两边的答案拿来,合并
    if(mid<y)res=res+query(rson,x,y);
    return res;
}
int n,m,q;
int main(){
    n=read(),m=read(),q=read();
    build(1,1,m);
    for(int opt,s,l,r;q--;){
        if((opt=read())&1){
            s=read(),l=read(),r=read();
            segment o=query(1,1,m,l,r);
            printf("%lld\n",(max(1ll,1ll*s>>o.fa)<<o.de)+o.v);//到根不能再上,所以取max
        }else{
            l=read(),r=read();
            update(1,1,m,l,r);
        }
    }
    return 0;
}
posted @ 2023-09-08 10:51  NBest  阅读(75)  评论(0)    收藏  举报