[ZJOI2019]线段树(线段树)
看到这题,首先想到将求和转期望,即每次操作进行概率为1/2,求节点打标记概率。
首先对于每次区间修改操作,对节点进行分类:
1、这个点和其父亲都和修改区间无交,这种情况可以无视。
2、这个点和修改区间无交但父亲和修改区间有交,这样该区间有无标记只和本身及是否存在一个祖先有标记相关。
3、这个点被修改区间覆盖,且父节点也被覆盖,则无变化。
4、这个点和修改区间有交但没有被完全包含,则不会有标记(因为要pushdown)。
然后记录该节点有标记的概率f,和祖先至少有一个有标记的概率g,然后根据上面表述的意思转移即可。
#include<bits/stdc++.h> using namespace std; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 const int N=2e5+7,mod=998244353,inv2=499122177; int n,m,pw[N],inv[N],f[N<<2],g[N<<2],s[N<<2],tag[N<<2]; void pushup(int rt){s[rt]=(1ll*s[rt<<1]+s[rt<<1|1]+f[rt])%mod;} void modify(int rt,int v){g[rt]=1ll*(g[rt]+pw[v]-1)*inv[v]%mod,tag[rt]+=v;} void pushdown(int rt) { if(!tag[rt])return; modify(rt<<1,tag[rt]),modify(rt<<1|1,tag[rt]); tag[rt]=0; } void update(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R) { tag[rt]++,f[rt]=1ll*(f[rt]+1)*inv2%mod,g[rt]=1ll*(g[rt]+1)*inv2%mod; pushup(rt);return; } if(L>r||R<l) { f[rt]=1ll*(f[rt]+g[rt])*inv2%mod; pushup(rt);return; } pushdown(rt); int mid=l+r>>1; f[rt]=1ll*f[rt]*inv2%mod,g[rt]=1ll*g[rt]*inv2%mod; update(L,R,lson),update(L,R,rson); pushup(rt); } int main() { scanf("%d%d",&n,&m); pw[0]=inv[0]=1;for(int i=1;i<=n;i++)pw[i]=2ll*pw[i-1]%mod,inv[i]=1ll*inv[i-1]*inv2%mod; int num=1; while(m--) { int op,l,r;scanf("%d",&op); if(op==2)printf("%d\n",1ll*s[1]*num%mod); else scanf("%d%d",&l,&r),num=2ll*num%mod,update(l,r,1,n,1); } }