zoukankan      html  css  js  c++  java
  • 【ZJOI2017】树状数组

    题目描述

    漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的 OI 比赛经历。那是一道基础的树状数组题。

    给出一个长度为 $n$ 的数组 $A$,初始值都为 $0$,接下来进行 $m$ 次操作,操作有两种:

    • $1~x$, 表示将 $A_x$ 变成 $(A_x + 1) mod{2}$。
    • $2~l~r$, 表示询问 $(sum_{i=l}^r A_i) mod{2}$。

    尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常 young 的她写了如下的算法:

    Algorithm 1

    其中 $mathrm{lowbit}(x)$ 表示数字 $x$ 最低的非 $0$ 二进制位,例如 $mathrm{lowbit}(5) = 1, mathrm{lowbit}(12) = 4$。进行第一类操作的时候就调用 $mathrm{Add}(x)$,第二类操作的时候答案就是 $mathrm{Query}(l, r)$。

    如果你对树状数组比较熟悉,不难发现可怜把树状数组写错了:$mathrm{Add}$ 和 $mathrm{Find}$ 中 $x$ 变化的方向反了。因此这个程序在最终测试时华丽的爆 $0$ 了。

    然而奇怪的是,在当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对拍的原因。

    现在,可怜想要算一下,这个程序回答对每一个询问的概率是多少,这样她就可以再次的感受到自己是一个多么非的人了。然而时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次第一类操作的 $x$ 的值,因此她假定这次操作的 $x$ 是在 $[l_i, r_i]$ 范围内等概率随机的。

    具体来说,可怜给出了一个长度为 $n$ 的数组 $A$,初始为 $0$,接下来进行了 $m$ 次操作:

    • $1~l~r$, 表示在区间 $[l, r]$ 中等概率选取一个 $x$ 并执行 $mathrm{Add}(x)$。
    • $2~l~r$, 表示询问执行 $mathrm{Query}(l, r)$ 得到的结果是正确的概率是多少。

    输入格式

    第一行输入两个整数 $n, m$。 接下来 $m$ 行每行描述一个操作,格式如题目中所示。

    输出格式

    对于每组询问,输出一个整数表示答案。如果答案化为最简分数后形如 $frac{x}{y}$,那么你只需要输出 $x imes y^{-1} mod{998244353}$ 后的值。(即输出答案模 $998244353$)。

    限制与约定

    测试点编号 $n$ $m$ 其他约定
    1$le 5$$le 10$
    2$le 50$$le 50$
    3
    4$le 3000$$le 3000$
    5
    6$le 10^5$$le 10^5$所有询问都在修改后
    7
    8
    9
    10

    对于 100% 的数据,保证 $1 le l le r le n$。

    时间限制:$4 exttt{s}$

    空间限制:$512 exttt{MB}$


    分析1

    打个暴力,小范围的找一下规律,就会发现,这个写错的树状数组,其实就是单点修改,查询后缀和

    那么因为所有的结果都对2取模,那么我们可以看成是查询后缀异或和

    这样一来,我们(Find(x))就是(XOR_{i=x}^{n} a_i)

    于是(Query(l,r))就是(XOR_{i=l-1}^{n} a_i oplus XOR_{i=r}^{n} a_i=XOR_{i=l-1}^{r-1} a_i)

    而实际,正确的答案应该是(XOR_{i=l}^{r} a_i),那么我们只要看(a_{l-1}oplus a_{r})的结果就行了

    但是,当(l=1)的时候,情况就不一样了,因为Find函数,特判了(l=1)的情况

    (Query(1,r)=XOR_{i=r}^{n} a_i),而实际需要的是(XOR_{i=l}^{r} a_i),那么就要看(XOR_{i=1}^{n(i eq r)} a_i),也就是(Toplus a_r)(T)表示(1)操作的次数,即修改的次数

    那么,这样一来就变成了单点修改问题,直接暴力解决,时间复杂度(O(nm)),可以得到50分

    #include<cstdio>  
    #include<iostream>  
    #include<algorithm>  
    #include<cstdlib>  
    #include<cstring>
    #include<string>
    #include<climits>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    #define LL long long
     
    using namespace std;
     
    inline char nc(){
      static char buf[100000],*p1=buf,*p2=buf;
      if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
      return *p1++;
    }
     
    inline void read(int &x){
      char c=nc();int b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
     
    inline void read(LL &x){
      char c=nc();LL b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
    
    inline int read(char *s)
    {
    	char c=nc();int len=0;
    	for(;!(c>='A' && c<='Z');c=nc()) if (c==EOF) return 0;
    	for(;(c>='A' && c<='Z');s[len++]=c,c=nc());
    	s[len++]='';
    	return len;
    }
    
    inline void read(char &x){
      for (x=nc();!(x>='A' && x<='Z');x=nc());
    }
    
    int wt,ss[19];
    inline void print(int x){
    
    	if (x<0) x=-x,putchar('-'); 
    	if (!x) putchar(48); else {
    	for (wt=0;x;ss[++wt]=x%10,x/=10);
    	for (;wt;putchar(ss[wt]+48),wt--);}
    }
    inline void print(LL x){
    	if (x<0) x=-x,putchar('-');
    	if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
    }
    
    int n,m,a[100010],c[100010],f[5];
    struct data
    {
    	LL x,y;
    }q[100010];
    LL Sum;
    const LL mo=998244353;
    
    LL GCD(LL x,LL y)
    {
    	if (x==0 || y==0) return 1LL;
    	LL r=x%y;
    	while (r!=0) x=y,y=r,r=x%y;
    	return y;
    }
    
    LL Power(LL x,LL y)
    {
    	LL res=1;
    	for(;y;y>>=1)
    	{
    		if (y&1) res=res*x%mo;
    		x=x*x%mo;
    	}
    	return res%mo;
    }
    
    LL P(LL x,LL y){return x*Power(y,mo-2)%mo;}
    LL jia(LL x,LL y){return (x+y)%mo;}
    LL jia(LL x,LL y,LL z){return ((x+y)%mo+z)%mo;}
    LL cheng(LL x,LL y){return x*y%mo;}
    
    LL calc(int l,int r)
    {
    	if (l==1)
    	{
    		f[0]=P(1LL,1LL),f[1]=0;
    		LL x,y;
    		for (int i=1;i<=Sum;i++)
    			if (q[i].x<=r && q[i].y>=r)
    			{
    				x=f[0],y=f[1];
    				f[0]=jia(cheng(y,P(1LL,q[i].y-q[i].x+1LL)),cheng(x,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)));
    				f[1]=jia(cheng(x,P(1LL,q[i].y-q[i].x+1LL)),cheng(y,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)));
    			}
    		x=f[Sum%2];
    		return x;
    	}
    	else
    	{
    		LL x,y,u,v;l--;
    		f[0]=P(1LL,1LL),f[1]=0,f[2]=0,f[3]=0;
    		for (int i=1;i<=Sum;i++)
    			if (q[i].x<=l && q[i].y>=l && q[i].x<=r && q[i].y>=r)
    			{
    				x=f[0],y=f[1],u=f[2],v=f[3];
    				f[0]=jia(cheng(x,P(q[i].y-q[i].x-1LL,q[i].y-q[i].x+1LL)),cheng(y,P(1LL,q[i].y-q[i].x+1LL)),cheng(u,P(1LL,q[i].y-q[i].x+1LL)));
    				f[1]=jia(cheng(y,P(q[i].y-q[i].x-1LL,q[i].y-q[i].x+1LL)),cheng(x,P(1LL,q[i].y-q[i].x+1LL)),cheng(v,P(1LL,q[i].y-q[i].x+1LL)));
    				f[2]=jia(cheng(u,P(q[i].y-q[i].x-1LL,q[i].y-q[i].x+1LL)),cheng(x,P(1LL,q[i].y-q[i].x+1LL)),cheng(v,P(1LL,q[i].y-q[i].x+1LL)));
    				f[3]=jia(cheng(v,P(q[i].y-q[i].x-1LL,q[i].y-q[i].x+1LL)),cheng(y,P(1LL,q[i].y-q[i].x+1LL)),cheng(u,P(1LL,q[i].y-q[i].x+1LL)));
    			}
    			else if (q[i].x<=l && q[i].y>=l)
    			{
    				x=f[0],y=f[1],u=f[2],v=f[3];
    				f[0]=jia(cheng(x,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(u,P(1LL,q[i].y-q[i].x+1LL)));
    				f[2]=jia(cheng(u,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(x,P(1LL,q[i].y-q[i].x+1LL)));
    				f[1]=jia(cheng(y,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(v,P(1LL,q[i].y-q[i].x+1LL)));
    				f[3]=jia(cheng(v,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(y,P(1LL,q[i].y-q[i].x+1LL)));
    			}
    			else if (q[i].x<=r && q[i].y>=r)
    			{
    				x=f[0],y=f[1],u=f[2],v=f[3];
    				f[0]=jia(cheng(x,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(y,P(1LL,q[i].y-q[i].x+1LL)));
    				f[1]=jia(cheng(y,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(x,P(1LL,q[i].y-q[i].x+1LL)));
    				f[2]=jia(cheng(u,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(v,P(1LL,q[i].y-q[i].x+1LL)));
    				f[3]=jia(cheng(v,P(q[i].y-q[i].x,q[i].y-q[i].x+1LL)),cheng(u,P(1LL,q[i].y-q[i].x+1LL)));
    			}
    		x=jia(f[0],f[3]);
    		return x;
    	}
    }
    
    int main()
    {
    	read(n);read(m);
    	Sum=0;
    	int x;LL y,z;
    	for (int i=1;i<=m;i++)
    	{
    		read(x);read(y);read(z);
    		if (x==1) Sum++,q[Sum].x=y,q[Sum].y=z;
    		else print(calc(y,z)),puts("");
    	}
    	return 0;
    }
    

    分析2

    这样一来,我们可以把问题转化到平面上来做

    对于一个修改([l,r]),我们可以转为平面上的点((l,r))

    那么,对于一个询问,我们可以分三类讨论

    第一类是仅包含左端点的,即(1≤x≤l-1,1≤y<r)

    第二类是仅包含右端点的,即(l≤x≤r,r≤y≤n)

    第三类是包含左右端点的,即(l≤x≤l-1,r≤y≤n)

    显然,前两部分可以合在一起做,不过似乎网上的代码大都是三部分合在一起做的,这或许也是我常数大的原因吧

    那么,我们就变成了动态二维数点问题,直接用二维线段树维护一下就好(我直接写了一棵四分树)

    对于第一类,我们保留端点为(1)的概率,合并两个点的概率(x)(y),显然可以保留其中一个,那么(P=x*(1-y)+y*(1-x))

    对于第二类,我们保留两端点相同的概率,合并两个点的概率(x)(y),显然可以两个相等得到,也可以是都不同得到,那么(P=x*y+(1-x)*(1-y))

    然后就是写代码的问题了,没什么细节问题,就是要注意常数问题

    #include<cstdio>  
    #include<iostream>  
    #include<algorithm>  
    #include<cstdlib>  
    #include<cstring>
    #include<string>
    #include<climits>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    #define LL long long
     
    using namespace std;
     
    inline char nc(){
      static char buf[100000],*p1=buf,*p2=buf;
      if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
      return *p1++;
    }
     
    inline void read(int &x){
      char c=nc();int b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
     
    inline void read(LL &x){
      char c=nc();LL b=1;
      for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
      for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
    }
    
    inline int read(char *s)
    {
    	char c=nc();int len=0;
    	for(;!(c>='A' && c<='Z');c=nc()) if (c==EOF) return 0;
    	for(;(c>='A' && c<='Z');s[len++]=c,c=nc());
    	s[len++]='';
    	return len;
    }
    
    inline void read(char &x){
      for (x=nc();!(x>='A' && x<='Z');x=nc());
    }
    
    int wt,ss[19];
    inline void print(int x){
    
    	if (x<0) x=-x,putchar('-');
    	if (!x) putchar(48); else {
    	for (wt=0;x;ss[++wt]=x%10,x/=10);
    	for (;wt;putchar(ss[wt]+48),wt--);}
    }
    inline void print(LL x){
    	if (x<0) x=-x,putchar('-');
    	if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
    }
    
    int n,m,S,a[100010],c[100010],f[5],Max;
    struct data
    {
    	LL x,y;
    }q[100010];
    LL Sum;
    const LL mo=998244353;
    struct st
    {
    	LL p1,p2;
    	int c1,c2,c3,c4;
    }tree[800010];
    
    LL Power(LL x,LL y)
    {
    	LL res=1;
    	for(;y;y>>=1)
    	{
    		if (y&1) res=res*x%mo;
    		x=x*x%mo;
    	}
    	return res%mo;
    }
    
    LL P(LL x,LL y){return x*Power(y,mo-2)%mo;}
    LL merge1(LL x,LL y){return ((x*(1LL+mo-y))%mo+(y*(1LL+mo-x))%mo)%mo;}
    LL merge2(LL x,LL y){return (x*y%mo+(1LL+mo-x)*(1LL+mo-y)%mo)%mo;}
    LL merge1(LL a,LL b,LL c,LL d){return merge1(merge1(a,b),merge1(c,d));}
    LL merge2(LL a,LL b,LL c,LL d){return merge2(merge2(a,b),merge2(c,d));}
    
    void change(int xq,int yq,int xl,int xr,int yl,int yr,int x,LL z1,LL z2)
    //z1是修改单个概率,z2是两个都不被修改的概率
    {
    	if (xl==xr && yl==yr){tree[x].p1=merge1(tree[x].p1,z1);tree[x].p2=merge2(tree[x].p2,z2);return ;}
    	int midx=xl+xr>>1,midy=yl+yr>>1;
    	if (xq<=midx && yq<=midy && xl<=midx && yl<=midy)
    	{
    		if (tree[x].c1==0) tree[x].c1=++S,tree[S].p1=0LL,tree[S].p2=1LL;
    		change(xq,yq,xl,midx,yl,midy,tree[x].c1,z1,z2);
    	}
    	else if (xq<=midx && yq>midy && xl<=midx && midy+1<=yr)
    	{
    		if (tree[x].c2==0) tree[x].c2=++S,tree[S].p1=0LL,tree[S].p2=1LL;
    		change(xq,yq,xl,midx,midy+1,yr,tree[x].c2,z1,z2);
    	}
    	else if (xq>midx && yq<=midy && midx+1<=xr && yl<=midy)
    	{
    		if (tree[x].c3==0) tree[x].c3=++S,tree[S].p1=0LL,tree[S].p2=1LL;
    		change(xq,yq,midx+1,xr,yl,midy,tree[x].c3,z1,z2);
    	}
    	else 
    	{
    		if (tree[x].c4==0) tree[x].c4=++S,tree[S].p1=0LL,tree[S].p2=1LL;
    		change(xq,yq,midx+1,xr,midy+1,yr,tree[x].c4,z1,z2);
    	}
    	tree[x].p1=merge1(tree[tree[x].c1].p1,tree[tree[x].c2].p1,tree[tree[x].c3].p1,tree[tree[x].c4].p1);
    	tree[x].p2=merge2(tree[tree[x].c1].p2,tree[tree[x].c2].p2,tree[tree[x].c3].p2,tree[tree[x].c4].p2);
    }
    
    LL query1(int xlq,int xrq,int ylq,int yrq,int xl,int xr,int yl,int yr,int x)
    {
    	if (x==0) return 0LL;
    	if (xlq<=xl && xrq>=xr && ylq<=yl && yrq>=yr) return tree[x].p1;
    	int midx=xl+xr>>1,midy=yl+yr>>1;LL res=0LL;
    	if (xlq<=midx && ylq<=midy && xl<=midx && yl<=midy) res=merge1(res,query1(xlq,xrq,ylq,yrq,xl,midx,yl,midy,tree[x].c1));
    	if (xlq<=midx && yrq>midy && xl<=midx && midy+1<=yr) res=merge1(res,query1(xlq,xrq,ylq,yrq,xl,midx,midy+1,yr,tree[x].c2));
    	if (xrq>midx && ylq<=midy && midx+1<=xr && yl<=midy) res=merge1(res,query1(xlq,xrq,ylq,yrq,midx+1,xr,yl,midy,tree[x].c3));
    	if (xrq>midx && yrq>midy && midx+1<=xr && midy+1<=yr) res=merge1(res,query1(xlq,xrq,ylq,yrq,midx+1,xr,midy+1,yr,tree[x].c4));
    	return res%mo;
    }
    
    LL query2(int xlq,int xrq,int ylq,int yrq,int xl,int xr,int yl,int yr,int x)
    {
    	if (x==0) return 1LL;
    	if (xlq<=xl && xrq>=xr && ylq<=yl && yrq>=yr) return tree[x].p2;
    	int midx=xl+xr>>1,midy=yl+yr>>1;LL res=1LL;
    	if (xlq<=midx && ylq<=midy && xl<=midx && yl<=midy) res=merge2(res,query2(xlq,xrq,ylq,yrq,xl,midx,yl,midy,tree[x].c1));
    	if (xlq<=midx && yrq>midy && xl<=midx && midy+1<=yr) res=merge2(res,query2(xlq,xrq,ylq,yrq,xl,midx,midy+1,yr,tree[x].c2));
    	if (xrq>midx && ylq<=midy && midx+1<=xr && yl<=midy) res=merge2(res,query2(xlq,xrq,ylq,yrq,midx+1,xr,yl,midy,tree[x].c3));
    	if (xrq>midx && yrq>midy && midx+1<=xr && midy+1<=yr) res=merge2(res,query2(xlq,xrq,ylq,yrq,midx+1,xr,midy+1,yr,tree[x].c4));
    	return res%mo;
    }
    
    int main()
    {
    	read(n);read(m);
    	int x,y,z;LL A,B,C,s=0;
    	memset(tree,0,sizeof(tree));
    	tree[0].p1=0LL,tree[0].p2=1LL;
    	tree[1].p1=0LL,tree[1].p2=1LL;S=1;
    	while (m--)
    	{
    		read(x);read(y);read(z);
    		if (x==1)
    		{
    			if (z-y+1>1) change(y,z,1,n,1,n,1,P(1LL,(LL)z-y+1),P((LL)z-y-1,(LL)z-y+1));
    			else change(y,z,1,n,1,n,1,P(1LL,(LL)z-y+1),0LL);
    			s++;
    		}
    		else
    		{
    			if (y>1)
    			{
    				A=query1(1,y-1,y-1,z-1,1,n,1,n,1);
    				B=query2(1,y-1,z,n,1,n,1,n,1);
    				C=query1(y,z,z,n,1,n,1,n,1);
    				print(((B*A%mo*C%mo+B*(1LL+mo-A)%mo*(1LL+mo-C)%mo)%mo+((1LL+mo-B)*A%mo*(1LL+mo-C)%mo+(1LL+mo-B)*C%mo*(1LL+mo-A)%mo)%mo)%mo),puts(""); 
    			}
    			else
    			{
    				if (s%2==0) print((1LL+mo-query1(1,z,z,n,1,n,1,n,1))%mo),puts("");
    				else print(query1(1,z,z,n,1,n,1,n,1)),puts("");
    			}
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    Java实现 LeetCode 792 自定义字符串排序(暴力)
    asp.net session对象的持久化
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 791 自定义字符串排序(桶排序)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    Java实现 LeetCode 790 多米诺和托米诺平铺(递推)
    小白也能看懂的约瑟夫环问题
  • 原文地址:https://www.cnblogs.com/xiejiadong/p/6800872.html
Copyright © 2011-2022 走看看