首页 > 代码库 > 【BZOJ4785】[Zjoi2017]树状数组 树套树(二维线段树)
【BZOJ4785】[Zjoi2017]树状数组 树套树(二维线段树)
【BZOJ4785】[Zjoi2017]树状数组
Description
漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历。那是一道基础的树状数组题。给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种:
Input
Output
Sample Input
1 3 3
2 3 5
2 4 5
1 1 3
2 2 5
Sample Output
0
665496236
//在进行完 Add(3) 之后, A 数组变成了 [0, 1, 1, 0, 0]。所以前两次询问可怜的程序答案都是1,因此第一次询问可怜一定正确,第二次询问可怜一定错误。
题解:发现这里给的树状数组的方向正好是反过来的,也就是说这里的树状数组维护的实际上是后缀xor和。那么后缀xor和与前缀xor和相等的情况就是:
[1,l-1]^[1,r]=[l-1,n]^[r,n] --> [l,r]=[l-1,r-1] ---> [l-1]=[r]
也就是说我们求的是l的值和r的值相等的概率。然后到这里,大部分题解都说“这变成了一个二维数点问题”,然而本蒟蒻一脸mengbi,所以,这里还是换一种方法讲吧。
我们用(a,b)表示a的值和b的值相等的概率。加入我们想要修改[l,r]中随机一个点,那么我们先考虑所有l<=a<b<=r的点对。
对于点对a,b,一次修改中它们最多只有一个数改变,我们设$q=1-{2\over r-l+1}$,表示a,b相等性不变的概率,设p表示原来a,b相等的概率,那么$p=pq+(1-p)(1-q)$。并且,我们要对[l,r]中所有的点对都进行这个计算,那么我们可以认为(a,b)是二维平面上的一个点,我们要修改的是(l,l)-(r,r)这个矩形,这可以用二维线段树维护。
//问题:对于某个树上的节点x,我们先给它打了个标记q1,有想给它打个标记q2,这两个标记该如何处理呢?自己推一推就知道,因为一开始的p都是1,那么先处理q1和先处理q2的结果是相同的(也就是说标记满足交换律),设p打了q1标记变成p‘,我们在同样的给p‘打个q2标记就行了。
再考虑a<l<=b<=r的点对(l<=a<=r<b的类似),这样的点对的相等性不变的概率就是$q=1-{1\over r-l+1}$。此时我们要修改的矩形就变成了(1,l-1)-(l,r),依旧二维线段树。
突然发现一种情况,当l=1时怎么办?因为l-1=0,所以此时要求的就是后缀xor和与前缀xor和相等的概率,单独维护一下就好了。
=======下面是二维线段树部分=======
本题要支持什么操作呢?矩形区间计算。因为二维线段树必须标记永久化,所以我们在第一位线段树时,每访问到一个合法的整区间,就进入第二维线段树去进行区间修改。这样,在查询的时候,我们的答案要将第一维线段树上 从根到叶子的所有节点的查询结果 全都算到一起,也就是说没经过一个节点就要更新答案。
#include <cstdio>#include <cstring>#include <iostream>#define z(_) (((_)%mod+mod)%mod)#define lson x<<1#define rson x<<1|1using namespace std;typedef long long ll;const ll mod=998244353;const int maxn=100010;int n,m,tot;ll inv(ll x){ ll z=1,y=mod-2; while(y) { if(y&1) z=z*x%mod; x=x*x%mod,y>>=1; } return z;}ll calc(ll a,ll b){ return z(a*b+(1-a)*(1-b));}int rd(){ int ret=0; char gc=getchar(); while(gc<‘0‘||gc>‘9‘) gc=getchar(); while(gc>=‘0‘&&gc<=‘9‘) ret=ret*10+gc-‘0‘,gc=getchar(); return ret;}int ls[maxn<<8],rs[maxn<<8],rt[maxn<<2];ll s[maxn<<8];void up1(int l,int r,int &x,int a,int b,ll c){ if(!x) x=++tot,s[x]=1; if(a<=l&&r<=b) { s[x]=calc(s[x],c); return ; } int mid=l+r>>1; if(a<=mid) up1(l,mid,ls[x],a,b,c); if(b>mid) up1(mid+1,r,rs[x],a,b,c);}ll q1(int l,int r,int x,int a){ if(!x) return 1; if(l==r) return s[x]; int mid=l+r>>1; if(a<=mid) return calc(s[x],q1(l,mid,ls[x],a)); else return calc(s[x],q1(mid+1,r,rs[x],a));}void up2(int l,int r,int x,int a,int b,int c,int d,ll e){ if(a<=l&&r<=b) { up1(1,n,rt[x],c,d,e); return ; } int mid=l+r>>1; if(a<=mid) up2(l,mid,lson,a,b,c,d,e); if(b>mid) up2(mid+1,r,rson,a,b,c,d,e);}ll q2(int l,int r,int x,int a,int b){ if(l==r) return q1(1,n,rt[x],b); int mid=l+r>>1; if(a<=mid) return calc(q1(1,n,rt[x],b),q2(l,mid,lson,a,b)); else return calc(q1(1,n,rt[x],b),q2(mid+1,r,rson,a,b));}int main(){ n=rd(),m=rd(); int i,a,b,c; ll p,q; for(i=1;i<=m;i++) { c=rd(),a=rd(),b=rd(); if(c==1) { p=inv(b-a+1); if(a>1) up2(0,n,1,1,a-1,a,b,z(1-p)),up2(0,n,1,0,0,0,a-1,0); if(b<n) up2(0,n,1,a,b,b+1,n,z(1-p)),up2(0,n,1,0,0,b+1,n,0); up2(0,n,1,a,b,a,b,z(1-2*p)),up2(0,n,1,0,0,a,b,p); } else printf("%lld\n",q2(0,n,1,a-1,b)); } return 0;}
【BZOJ4785】[Zjoi2017]树状数组 树套树(二维线段树)