[BZOJ4785][ZJOI2017]树状数组(概率+二维线段树)
4785: [Zjoi2017]树状数组
Time Limit: 40 Sec Memory Limit: 512 MB
Submit: 297 Solved: 195
[Submit][Status][Discuss]Description
漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历。那是一道
基础的树状数组题。给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种:1 x,表示将 Ax 变成 (Ax + 1) mod 2。2 l r,表示询问 sigma(Ai) mod 2,L<=i<=r尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常young 的她写了如下的算法:1: function Add(x)2: while x > 0 do3: Ax ← (Ax + 1) mod 24: x ← x ? lowbit(x)5: end while6: end function7:8: function Find(x)9: if x == 0 then10: return 011: end if12: ans ← 013: while x ≤ n do14: ans ← (ans + Ax) mod 215: x ← x + lowbit(x)16: end while17: return ans18: end function19:20: function Query(l, r)21: ansl ← Find(l ? 1)22: ansr ← Find(r)23: return (ansr ? ansl + 2) mod 224: end function其中 lowbit(x) 表示数字 x 最?的非 0 二进制位,例如 lowbit(5) = 1, lowbit(12) = 4。进行第一类操作的时候就调用 Add(x),第二类操作的时候答案就是 Query(l, r)。如果你对树状数组比较熟悉,不难发现可怜把树状数组写错了: Add和Find 中 x 变化的方向反了。因此这个程序在最终测试时华丽的爆 0 了。然而奇怪的是,在当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对拍的原因。现在,可怜想要算一下,这个程序回答对每一个询问的概率是多少,这样她就可以再次的感受到自己是一个多么非的人了。然而时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次第一类操作的 x的值,因此她假定这次操作的 x 是在 [li, ri] 范围内 等概率随机 的。具体来说,可怜给出了一个长度为 n 的数组 A,初始为 0,接下来进行了 m 次操作:1 l r,表示在区间 [l, r] 中等概率选取一个 x 并执行 Add(x)。2 l r,表示询问执行 Query(l, r) 得到的结果是正确的概率是多少。Input
第一行输入两个整数 n, m。接下来 m 行每行描述一个操作,格式如题目中所示。N<=10^5,m<=10^5,1<=L<=R<=NOutput
对于每组询问,输出一个整数表示答案。如果答案化为最简分数后形如 x/y,那么你只需要输出 x*y^?1 mod 998244353 后的值。(即输出答案模 998244353)。Sample Input
5 5
1 3 3
2 3 5
2 4 5
1 1 3
2 2 5Sample Output
1
0
665496236
//在进行完 Add(3) 之后, A 数组变成了 [0, 1, 1, 0, 0]。所以前两次询问可怜的程序答案都是
1,因此第一次询问可怜一定正确,第二次询问可怜一定错误。
首先经过分析证明可得,树状数组只是一层外衣,实际上题目就是求[l-1,r-1]和[l,r]的改变次数的差为偶数的概率,也就是l-1和r改变次数差为偶数的概率。(l==1的情况要特殊处理,也就是[1,r-1]和[r+1,n]的总改变次数差为偶数的概率)
想到这里之后,我们会有一个看似正确的直觉:可以通过动规+前缀和求出每个数被改变奇数次和偶数次的概率。但是实际上由于动规方程里的并不是互斥事件,所以概率不可以直接相乘。
所以我们可以肯定,一定是对每个数,依次遍历所有的修改,已修改时间为下标做DP。这样就有了一个简单的50分做法。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=l; i<=r; i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=3005,md=998244353; 10 int dp[N],n,m,op,l,r,cnt; 11 struct node{ int l,r,v; }G[N]; 12 13 int ksm(int x,int y){ 14 int res=1; 15 for(; y; y>>=1,x=(ll)x*x%md) 16 if (y&1) res=(ll)res*x%md; 17 return res; 18 } 19 20 int main(){ 21 freopen("bit.in","r",stdin); 22 freopen("bit.out","w",stdout); 23 scanf("%d%d",&n,&m); 24 while (m--){ 25 scanf("%d%d%d",&op,&l,&r); 26 if (op==1) G[cnt++]=(node){l,r,ksm(r-l+1,md-2)}; 27 else{ 28 int ans=1; 29 if(l==1){ 30 rep(i,0,cnt) 31 if(G[i].r>=l){ 32 int len=(G[i].r-max(l,G[i].l)+1-(G[i].r>=r&&G[i].l<=r)); 33 len = len*(ll)G[i].v%md; 34 ans=(((ll)ans*(1-len)+(ll)(1-ans)*len)%md+md)%md; 35 } 36 }else 37 rep(i,0,cnt) 38 if(G[i].l<=l-1&&G[i].r>=r){ 39 int len=G[i].v*(ll)2%md; 40 ans=(((ll)ans*(1-len)+(ll)(1-ans)*len)%md+md)%md; 41 }else if((G[i].l<=l-1&&G[i].r>=l-1)||(G[i].l<=r&&G[i].r>=r)){ 42 int len=G[i].v; 43 ans=(((ll)ans*(1-len)+(ll)(1-ans)*len)%md+md)%md; 44 } 45 printf("%d\n",ans); 46 } 47 } 48 return 0; 49 }
那么如果想到了这里,已经不难进一步想出用数据结构来维护了。将每个询问映射成二维平面上的点(l-1,r),(类似BZOJ4826影魔),然后用二维线段树实现矩形区间修改,l==1的情况只要一维线段树即可。
UOJ上可能有BUG,在线自定义测试的时候程序正常运行并返回正确结果,同样的数据提交评测却无限RE。能过官方数据,BZOJ上AC,UOJ上的HACK数据没有测。
现在总结一下写代码的时候需要注意的地方。
1.每写完一段停下来检查一下
2.修改程序的时候要慢一点,检查修改是否正确并思考是否有类似地方需要修改。
3.调试时多想想平时遇到的问题优先考虑。
4.多总结遇到的错误(特别是模板方面),以1A为最终目标。
1 #include<cstdio> 2 #include<algorithm> 3 #define lc (x<<1) 4 #define rc ((x<<1)|1) 5 #define rep(i,l,r) for (int i=l; i<=r; i++) 6 using namespace std; 7 8 const int N=200100,md=998244353; 9 int n,m,cnt,nd,op,l,r,rt[N*3],P[N*180]; 10 struct D{ int ls,rs; }a[N*180]; 11 int F(int x,int y){ return (1ll*x*y%md+1ll*(1-x+md)*(1-y+md)%md)%md; } 12 13 int ksm(int a,int b){ 14 int res; 15 for (res=1; b; a=(1ll*a*a)%md,b>>=1) 16 if (b & 1) res=(1ll*res*a)%md; 17 return res; 18 } 19 20 void mdf(int &x,int L,int R,int l,int r,int k){ 21 if (!x) x=++nd,P[x]=1; 22 if (L==l && r==R) { P[x]=F(P[x],k); return; } 23 int mid=(L+R)>>1; 24 if (r<=mid) mdf(a[x].ls,L,mid,l,r,k); 25 else if (l>mid) mdf(a[x].rs,mid+1,R,l,r,k); 26 else mdf(a[x].ls,L,mid,l,mid,k),mdf(a[x].rs,mid+1,R,mid+1,r,k); 27 } 28 29 int que(int x,int L,int R,int pos){ 30 if (!x) return 1; 31 if (L==R) return P[x]; int mid=(L+R)>>1; 32 if (pos<=mid) return F(P[x],que(a[x].ls,L,mid,pos)); 33 else return F(P[x],que(a[x].rs,mid+1,R,pos)); 34 } 35 36 void Mdf(int x,int L,int R,int l,int r,int xx,int yy,int k){ 37 if (L==l && r==R){ mdf(rt[x],0,n+1,xx,yy,k); return; } 38 int mid=(L+R)>>1; 39 if (r<=mid) Mdf(lc,L,mid,l,r,xx,yy,k); 40 else if (l>mid) Mdf(rc,mid+1,R,l,r,xx,yy,k); 41 else Mdf(lc,L,mid,l,mid,xx,yy,k),Mdf(rc,mid+1,R,mid+1,r,xx,yy,k); 42 } 43 44 int Que(int x,int L,int R,int posx,int posy){ 45 int res=1; 46 if (rt[x]) res=F(res,que(rt[x],0,n+1,posy)); 47 if (L==R) return res; int mid=(L+R)>>1; 48 if (posx<=mid) res=F(res,Que(lc,L,mid,posx,posy)); 49 else res=F(res,Que(rc,mid+1,R,posx,posy)); 50 return res; 51 } 52 53 int main(){ 54 freopen("bit.in","r",stdin); 55 freopen("bit.out","w",stdout); 56 scanf("%d%d",&n,&m); 57 rep(i,1,m){ 58 scanf("%d%d%d",&op,&l,&r); 59 if (op==1){ 60 int p=ksm(r-l+1,md-2); 61 if (l>1) Mdf(1,1,n,1,l-1,l,r,(1-p+md)%md),mdf(rt[0],1,n,1,l-1,0); 62 if (r<n) Mdf(1,1,n,l,r,r+1,n,(1-p+md)%md),mdf(rt[0],1,n,r+1,n,0); 63 if (l!=r) Mdf(1,1,n,l,r,l,r,(1-p*2+md+md)%md); 64 mdf(rt[0],1,n,l,r,p); 65 }else{ 66 if (l==1) printf("%d\n",que(rt[0],1,n,r)); else printf("%d\n",Que(1,1,n,l-1,r)); 67 } 68 } 69 return 0; 70 }