洛谷P5280/LOJ3043/UOJ467/BZOJ5515[ZJOI2019]线段树

首先,不难看出$k$次修改后的树有$2^k$棵,所以暴力无疑只有20pts那不就是我吗

所以,我们要把$2^{k}$棵树的维护变成一棵树。记$P_o$为$o$在这些树有标记的概率,那么答案就是$\sum{P_o}*2^{k}$。但由于有$pushdown$,一个结点在被访问到时,只要它和它的祖先中有一个有标记,它就会有标记,所以我们也要记录$o$和$o$的祖先中有至少一个有标记的概率$Pf_o$

现在,我们把每次修改影响的结点分类:

1. 被打标记的结点

在发生修改一半的树中,它被打了标记,有标记的概率为$1$,另外一半的概率不变,所以$P_o=\frac{P_o+1}{2}$,同理,$Pf_o=\frac{Pf_o+1}{2}$。

2. 被$modify$访问,但未打标记的结点

在发生修改一半的树中,它和祖先的标记全部被下推,另外一半不变。所以$P_o=\frac{P_o}{2},Pf_o=\frac{Pf_o}{2}$。

3. 被$pushdown$访问,但未被$modify$访问的结点

它们的$Pf_o$不变(祖先和它之间推不推都一样),但只要祖先有标记,它就会因为$pushdown$带上标记,所以$P_o=\frac{P_o+Pf_o}{2}$。

4. 被打标记的结点的子树

它们的$P_o$无疑不变,但在修改之后,其祖先必定带有标记,所以$Pf_o=\frac{Pf_o+1}{2}$。

这一类结点可能有$O(n)$个,所以我们不能直接改,但可以发现修改其实就是乘上$\frac{1}{2}$再加$\frac{1}{2}$,所以像线段树2一样维护懒标记就可以了

#include<cstdio>
typedef long long ll;
const int mod=998244353;
const int inv2=499122177;  //2的逆元频繁使用,先存下来
const int N=100050;
char rB[1<<21],*S,*T,wB[1<<21];
int wp=-1;
inline char gc(){return S==T&&(T=(S=rB)+fread(rB,1,1<<21,stdin),S==T)?EOF:*S++;}
inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;}
inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
short buf[15];
inline void wt(int x){
    short l=-1;
    while(x>9){
        buf[++l]=x%10;
        x/=10;
    }
    pc(x|48);
    while(l>=0)pc(buf[l--]|48);
    pc('\n');
}
int p[N<<2],pf[N<<2],mulv[N<<2],addv[N<<2],sum[N<<3],x,y;  //在这个写法中叶子结点也会pushup,所以要sum要额外开大
void build(int o,int L,int R){
    mulv[o]=1;
    if(L<R){
        int lc=o<<1,rc=lc|1,M=L+R>>1;
        build(lc,L,M);build(rc,M+1,R);
    }
}
inline void pushup(int o){
    int lc=o<<1,rc=lc|1;
    sum[o]=((ll)p[o]+sum[lc]+sum[rc])%mod;
}
inline void pushdown(int o){
    int lc=o<<1,rc=lc|1;
    if(mulv[o]!=1){
        mulv[lc]=(ll)mulv[lc]*mulv[o]%mod;addv[lc]=(ll)addv[lc]*mulv[o]%mod;pf[lc]=(ll)pf[lc]*mulv[o]%mod;
        mulv[rc]=(ll)mulv[rc]*mulv[o]%mod;addv[rc]=(ll)addv[rc]*mulv[o]%mod;pf[rc]=(ll)pf[rc]*mulv[o]%mod;
        mulv[o]=1;
    }
    if(addv[o]){
        if((addv[lc]+=addv[o])>=mod)addv[lc]-=mod;if((pf[lc]+=addv[o])>=mod)pf[lc]-=mod;
        if((addv[rc]+=addv[o])>=mod)addv[rc]-=mod;if((pf[rc]+=addv[o])>=mod)pf[rc]-=mod;
        addv[o]=0;
    }
}
void update(int o,int L,int R){
    if(x<=L&&y>=R){  //第1类
        p[o]=((ll)p[o]*inv2+inv2)%mod;
        pf[o]=((ll)pf[o]*inv2+inv2)%mod;
        mulv[o]=(ll)mulv[o]*inv2%mod;
        addv[o]=((ll)addv[o]*inv2+inv2)%mod;  //为第4类的修改打懒标记
    }else{
        int lc=o<<1,rc=lc|1,M=L+R>>1;
        pushdown(o);
        if(x<=M)update(lc,L,M);
        else{
            p[lc]=((ll)p[lc]*inv2+(ll)pf[lc]*inv2)%mod;
            pushup(lc);
        }  //第3类
        if(y>M)update(rc,M+1,R);
        else{
            p[rc]=((ll)p[rc]*inv2+(ll)pf[rc]*inv2)%mod;
            pushup(rc);
        }
        p[o]=(ll)p[o]*inv2%mod;pf[o]=(ll)pf[o]*inv2%mod;  //第2类
    }
    pushup(o);
}
int main(){
    int n=rd(),q=rd(),t,k=1;  //用k存当前有多少棵树
    build(1,1,n);
    while(q--){
        t=rd();
        if(t==1){
            x=rd();y=rd();
            update(1,1,n);
            if((k<<=1)>=mod)k-=mod;
        }else if(t==2)wt((ll)sum[1]*k%mod);
    }
    flush();
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/sunshine-chen/p/11258796.html