【洛谷3688】[ZJOI2017] 树状数组(二维线段树)
- 给定一个长度为\(n\)的数组,有两种操作:
- 随机给区间\([l,r]\)中某一位置的值加上\(1\)。
- 询问区间\([l,r]\)所有位置的和模\(2\)的余数。
- 对于每个询问,求如果把树状数组写反了,与正确的答案相同的概率。
- \(n\le10^5\)
感觉这道题应该不是很难吧,至少我都能自己做出来。
不过这可能也是得益于不小心看到了二维线段树这一标签。。。
题意转化
注意这里的询问是以\(2\)为模数的,而\(x\equiv -x(mod\ 2)\)。
发现树状数组写反了,就意味着现在得到的是区间\([l-1,r-1]\)所有位置的和。
那么也就是求\(a_{l-1}\)和\(a_r\)相同的概率。
二维线段树
考虑我们用\((i,j)\)表示\(i\)和\(j\)两个位置值相同的概率。
对于一次修改\([l,r]\),令\(t=\frac{1}{r-l+1}\),然后发现对于\((i,j)\)有以下几种变化的可能:
- \(i,j\)都不在\([l,r]\)范围内,无影响。
- \(i,j\)都在\([l,r]\)范围内,有\(2t\)的概率选中其中一个数导致值改变,\(1-2t\)的概率没选中。
- \(i,j\)有且仅有一个在\([l,r]\)范围内,有\(t\)的概率选择区间中那个数导致值改变,\(1-t\)的概率没选中。
考虑如何合并两个值不变的概率\(x,y\),容易发现就是\(x\times y+(1-x)\times(1-y)\)。(即要么都不变,要么都变)
那么就可以直接拿二维线段树维护了。
具体实现时为了好写,可以考虑标记永久化。
\(l=1\)的特殊情况
注意,其实我们还要特殊处理\(l=1\),即\(l-1=0\)的情况。
此时得到的应该是\([r,n]\)所有位置的和,而本应得到的是\([1,r]\)所有位置的和,也就是要求出除第\(r\)个位置以外所有数之和为\(0\)的概率。
可以发现一个位置与\(0\)的答案值不变的概率就是它作为被修改位置的概率。
这其实和一般情况还是基本一致的,只要单独对这种情况开一个\(0\)号节点就好了,省得重写一个线段树。
代码:\(O(nlog^2n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LN 20
#define X 998244353
using namespace std;
int n;I int G(CI x,CI y) {return (1LL*x*y+1LL*(1-x+X)*(1-y+X))%X;}//合并两个值不变的概率
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
class SegSegTree//标记永久化二维线段树
{
private:
#define PT CI l=0,CI r=n
#define LT l,mid
#define RT mid+1,r
int Rt[N<<2];class SegmentTree
{
private:
int Nt;struct node{int V,S[2];}O[N*LN*LN+5];
public:
I void U(int& rt,CI L,CI R,CI v,PT)//区间修改
{
if(!rt&&(O[rt=++Nt].V=1),L<=l&&r<=R) return (void)(O[rt].V=G(O[rt].V,v));
RI mid=l+r>>1;L<=mid&&(U(O[rt].S[0],L,R,v,LT),0),R>mid&&(U(O[rt].S[1],L,R,v,RT),0);
}
I int Q(CI rt,CI y,PT)//单点询问
{
if(!rt) return 1;if(l==r) return O[rt].V;RI mid=l+r>>1;
return G(y<=mid?Q(O[rt].S[0],y,LT):Q(O[rt].S[1],y,RT),O[rt].V);
}
}S;
public:
I void U(CI L,CI R,CI _l,CI _r,CI v,PT,CI rt=1)//区间修改
{
if(L<=l&&r<=R) return S.U(Rt[rt],_l,_r,v);RI mid=l+r>>1;
L<=mid&&(U(L,R,_l,_r,v,LT,rt<<1),0),R>mid&&(U(L,R,_l,_r,v,RT,rt<<1|1),0);
}
I int Q(CI x,CI y,PT,CI rt=1)//单点询问
{
if(l==r) return S.Q(Rt[rt],y);RI mid=l+r>>1;
return G(x<=mid?Q(x,y,LT,rt<<1):Q(x,y,RT,rt<<1|1),S.Q(Rt[rt],y));
}
}T;
int main()
{
RI Qt,op,x,y,t;scanf("%d%d",&n,&Qt);W(Qt--) switch(scanf("%d%d%d",&op,&x,&y),op)
{
case 1:t=QP(y-x+1,X-2),T.U(x,y,x,y,(1-2LL*t%X+X)%X),T.U(0,0,x,y,t),//两个都在区间中
x^1&&(T.U(1,x-1,x,y,(1-t+X)%X),T.U(0,0,1,x-1,0),0),//有且仅有一个
y^n&&(T.U(x,y,y+1,n,(1-t+X)%X),T.U(0,0,y+1,n,0),0);break;//有且仅有一个
case 2:printf("%d\n",T.Q(x-1,y));break;//查询x-1和y值相同的概率
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒