[ZJOI2017]树状数组

神仙题

就贴一个\(loj\)的链接吧,因为也就只有\(loj\)能过

发现可怜的树状数组写反了,那么根据经验可怜求得其实就是后缀和

发现都是在二进制下进行的操作,于是那个减法和加法没有什么区别,我们可以看成是后缀\(l-1\)的和减掉后缀\(r\)的和

就是

\[\sum_{i=l-1}^na_i-\sum_{i=r}^na_i \]

发现求得就是\([l-1,r-1]\)这段区间的和

我们考虑一下这一段区间和\([l,r]\)的差别

发现\([l-1,r-1]-a_{l-1}+a_r=[l,r]\)

所以如果\(a_r=a_{l-1}\),我们就会发现这两段区间区间和是相等的

现在我们的问题就变成了求\(a_{l-1}\)\(a_r\)相等的概率了

\(dp_{i,0/1}\)表示\(i\)位置是\(0/1\)的概率

显然对于\(i\in[l,r]\),有这样的转移

\[dp_{i,0}=\frac{1}{r-l+1}dp'_{i,1}+\frac{r-l}{r-l+1}dp'_{i,0} \]

\[dp_{i,1}=\frac{1}{r-l+1}dp'_{i,0}+\frac{r-l}{r-l+1}dp'_{i,1} \]

发现我们可以把转移写成矩阵的形式

\[\begin{bmatrix}dp'_{i,0}\\dp'_{i,1}\end{bmatrix}\times \begin{bmatrix}\frac{r-l}{r-l+1}\ \frac{1}{r-l+1}\\\frac{1}{r-l+1}\ \frac{r-l}{r-l+1}\end{bmatrix}=\begin{bmatrix}dp_{i,0}\\dp_{i,1}\end{bmatrix} \]

这样我们就可以使用数据结构来维护矩阵乘法了

这样这道题就做完了吗?

显然不是

发现如果有两个位置\(i,j\in[l,r]\),我们会把这两个位置都修改一遍,如果再查询这两个位置是否相等的话,我们就相当于在\([l,r]\)这个区间选择了两个数加\(1\)

这一点也不科学啊

我们考虑一段区间同时包含了我们当前的两个询问点\(i,j\)

\(\frac{2}{r-l+1}\)的概率使得其中一个被加\(1\)\(\frac{r-l-1}{r-l+1}\)的概率加到了某一个没有什么影响的数上面去了

如果一个数加\(1\),那么我们的判断条件就从两个数相同变成了两个数不同,也就是要求发生改变

于是我们再来一个\(f_{0/1}\)表示当前的要求是两个数不同/相同的概率

这里也有一个矩阵

\[\begin{bmatrix}f'_{0}\\f'_{1}\end{bmatrix}\times \begin{bmatrix}\frac{r-l-1}{r-l+1}\ \frac{2}{r-l+1}\\\frac{2}{r-l+1}\ \frac{r-l-1}{r-l+1}\end{bmatrix}=\begin{bmatrix}f_{0}\\f_{1}\end{bmatrix} \]

对于\(i,j\)询问的答案就应该是

\[f_0(dp_{i,0}\times dp_{j,1}+dp_{i,1}\times dp_{j,0})+f_1(dp_{i,0}\times dp_{j,0}+dp_{i,1}\times dp_{j,1}) \]

于是我们需要处理出所有的包含\(i\)不包含\(j\),包含\(j\)不包含\(i\)的区间的矩阵乘积,求出\(dp_{i,0},dp_{i,1},dp_{j,0},dp_{j,1}\)

以及所有包含\(i\)又包含\(j\)的区间的矩阵乘积,求出\(f_0,f_1\)

发现这是一个二维的关系,我们可以直接上树套树

看起来就解决了

但是还是没有完,我们认真读题会发现在\(l-1=0\)的时候给出的代码特判退出了

那么我们就没有办法求\(a_{l-1}=a_r\)的概率了,发现这个时候可怜求得其实是\(r\)的后缀和

现在我们的问题变成了判断\(r\)的前缀和等于后缀和的概率

注意到这个前缀和后缀有一个公共点是\(r\),也就是\(r\)被修改是没有什么影响的

我们来一个树状数组,求出\([1,r-1]\)\([r+1,n]\)这两段的和

求得就是有多少段区间被\([1,r-1]\)\([r+1,n]\)完全包含,这些区间无论如何随机都一定能让这段区间的和加\(1\)

之后对于那些跨过\(r\)的区间,我们有\(\frac{1}{len}\)的概率选中\(r\)这个位置使得没什么影响,\(\frac{len-1}{len}\)的概率选在前缀和后缀中

我们还是可以写成矩阵来进行转移,于是还是需要一个树套树来维护

现在终于做完了,但还是二维线段树的空间太大了实在开不过去了

仅仅能在\(loj\)上过的代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=1e5+5;
const int M=2e7+10;
const LL mod=998244353;
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
void exgcd(LL a,LL b,LL &x,LL &y) {if(!b) {x=1,y=0;return;}exgcd(b,a%b,y,x);y-=a/b*x;}
inline LL getInv(LL a) {LL x,y;exgcd(a,mod,x,y);return (x%mod+mod)%mod;}
struct mat{int a[2][2];};
inline mat operator*(mat a,mat b) {
	mat c;
	c.a[0][0]=((LL)a.a[0][0]*(LL)b.a[0][0]%mod+(LL)a.a[0][1]*(LL)b.a[1][0]%mod)%mod;
	c.a[0][1]=((LL)a.a[0][0]*(LL)b.a[0][1]%mod+(LL)a.a[0][1]*(LL)b.a[1][1]%mod)%mod;
	c.a[1][0]=((LL)a.a[1][0]*(LL)b.a[0][0]%mod+(LL)a.a[1][1]*(LL)b.a[1][0]%mod)%mod;
	c.a[1][1]=((LL)a.a[1][0]*(LL)b.a[0][1]%mod+(LL)a.a[1][1]*(LL)b.a[1][1]%mod)%mod;
	return c;
}
int n,m,cnt;
LL dp[2][2],f[2],g[2];
int ls[maxn<<2],rs[maxn<<2],pos[maxn];
void build(int x,int y,int i) {
	ls[i]=x,rs[i]=y;
	if(x==y) {pos[x]=i;return;}
	int mid=x+y>>1;
	build(x,mid,i<<1);build(mid+1,y,i<<1|1);
}
int l[M],r[M];mat d[M];
int add(int now,int pos,int x,int y,mat v) {
	if(!now) {
		now=++cnt;
		d[now].a[0][0]=d[now].a[1][1]=1;
	}
	d[now]=d[now]*v;
	if(x==y) return now;
	int mid=x+y>>1;
	if(pos<=mid) l[now]=add(l[now],pos,x,mid,v);
		else r[now]=add(r[now],pos,mid+1,y,v);
	return now;
}
mat ask(int now,int x,int y,int lx,int ry) {
	if(!now) return d[0];
	if(x<=lx&&y>=ry) return d[now];
	int mid=lx+ry>>1;
	if(y<=mid) return ask(l[now],x,y,lx,mid);
	if(x>mid) return ask(r[now],x,y,mid+1,ry);
	return ask(l[now],x,y,lx,mid)*ask(r[now],x,y,mid+1,ry);
}
struct Segment_Tree {
	int rt[maxn*3];
	void change(int x,int y,mat v) {
		int i=pos[x];
		while(i) 
			rt[i]=add(rt[i],y,1,n,v),i>>=1;
	}
	mat query(int x,int y,int lx,int ry,int i) {
		if(x<=ls[i]&&y>=rs[i]) return ask(rt[i],lx,ry,1,n);
		int mid=ls[i]+rs[i]>>1;
		if(y<=mid) return query(x,y,lx,ry,i<<1);
		if(x>mid) return query(x,y,lx,ry,i<<1|1);
		return query(x,y,lx,ry,i<<1)*query(x,y,lx,ry,i<<1|1);
	}
}A,B,K;
struct Bit {
	int c[maxn];
	#define lb(x) (x&-x)
	inline void add(int x) {
		for(re int i=x;i<=n;i+=lb(i)) c[i]^=1;
	}
	inline int ask(int x) {
		int now=0;
		for(re int i=x;i;i-=lb(i)) now^=c[i];
		return now;
	} 
}C,D;
int main() {
	n=read(),m=read();
	build(1,n,1);
	d[0].a[0][0]=1;
	d[0].a[1][1]=1;
	int opt,x,y;
	int h=0;
	while(m--) {
		opt=read(),x=read(),y=read();
		if(opt==1) {
			mat v;h^=1;
			C.add(x),D.add(y);
			LL len=y-x+1;
			LL inv=getInv(len);
			v.a[1][1]=v.a[0][0]=(len-1)*inv%mod;
			v.a[0][1]=v.a[1][0]=inv;
			A.change(x,y,v);
			std::swap(v.a[0][0],v.a[0][1]);
			std::swap(v.a[1][0],v.a[1][1]);
			K.change(x,y,v);
			if(x==y) continue;
			v.a[1][1]=v.a[0][0]=(len-2)*inv%mod;
			v.a[1][0]=v.a[0][1]=2ll*inv%mod;
			B.change(x,y,v);
		}
		if(opt==2) {
			x--;
			mat p1,p2,p3;
			if(!x) {
				p1=K.query(1,y,y,n,1);
				int o=D.ask(y-1),t=(h^C.ask(y));
				if(o==t) printf("%d\n",p1.a[1][1]);
					else printf("%d\n",p1.a[0][1]);
				continue;
			}
			p1=A.query(1,x,x,y-1,1);
			p2=A.query(x+1,y,y,n,1);
			p3=B.query(1,x,y,n,1);
			dp[0][0]=p1.a[0][1],dp[0][1]=p1.a[1][1];
			dp[1][0]=p2.a[0][1],dp[1][1]=p2.a[1][1];
			f[0]=p3.a[0][1],f[1]=p3.a[1][1];
			g[0]=dp[0][0]*dp[1][0]%mod+dp[0][1]*dp[1][1]%mod;
			g[0]%=mod;
			g[1]=dp[0][1]*dp[1][0]%mod+dp[0][0]*dp[1][1]%mod;
			g[1]%=mod;
			printf("%lld\n",(f[0]*g[1]%mod+f[1]*g[0]%mod)%mod);
		}
	}
	return 0;
}

upd

发现正解好简单,我真是一个思博,活该被卡

posted @ 2019-03-29 11:03  asuldb  阅读(185)  评论(0编辑  收藏  举报