洛谷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; }