作诗

首先将原区间分块(设块的大小是T)

先处理处每一个数字的vector,蒲公英那题的vector一样

然后处理出\(f[i][j]\)数组,表示第\(i\)个块到第\(j\)个块之间出现了偶数次数字的个数

具体见代码,这里主要讲一下时间复杂度

时间复杂度不是\(O(n^2)\),实际上,应该是\(\sum_{i=0}^{\frac{N}{T}} N-i \cdot T=O(\frac{N^2}{T})\),因为每次都少扫描了一个块长,而不是每次只少扫描一个数。以后分块题目这里的时间复杂度估计千万别错了

对于每一个询问

如果询问的端点再同一个块里面,暴力循环记录每一个数字的出现次数(cnt数组)输出答案,然后再暴力循环把每一个数字对cnt数组的改变给变回来。复杂度\(O(T)\)

如果不在同一块里面,设左端点的块为\(p\),右端点的块为\(q\)

首先让ans加上\([p+1,q-1]\)这些块里面的num之和

对需要暴力处理的每一个数字,统计出来它在暴力区间里面的个数和整个区间的个数,分别为a和b,那么\(b-a\)就是这个数字在块里面的出现次数

\(b-a\)为奇数且a为奇数,那么ans++

\(b-a\)为偶数且a为奇数,那么ans--

复杂度为\(O(MT^2)\)

所以取\(T=\sqrt[3]{\frac{N^2}{2M}}\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
int n,c,m;
int a[N];
int L[N],R[N];
int f[4000][4000],belong[N],Count[N];
bool num[N],mark[N];
vector<int> pos[N];
int getnum(int x,int y,int k)
{
	if(x>y) return 0;
	int l=0,r=pos[k].size()-1,mid,res1,res2;
	//这里其实是有可能会出错的
	//如果某一时刻l=r=0,就死循环了
	//但就此题数据而言不会
	//然而以后二分时还是要注意尽量区间别弄到0 
	if(x>pos[k][r]||y<pos[k][0]) return 0;
	while(l<=r)
	{
		mid=l+r>>1;
		if(pos[k][mid]>=x)
		{
			res1=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	l=0,r=pos[k].size()-1;
	while(l<=r)
	{
		mid=l+r>>1;
		if(pos[k][mid]<=y)
		{
			res2=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	return max(res2-res1+1,0);//一定要注意res2小于res1的情况 
}
int main()
{
	scanf("%d%d%d",&n,&c,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		pos[a[i]].push_back(i);
	}
	int t=pow((ll)n*n/(2*m),1.0/3),cnt=n/t;
	//注意pow函数的用法
	//pow的两个参数都是double,可以这么用 
	for(int i=1;i<=cnt;i++)
	{
		L[i]=(i-1)*t+1;
		R[i]=i*t;
	}
	if(R[cnt]<n) 
	{
		cnt++;
		L[cnt]=R[cnt-1]+1;
		R[cnt]=n;
	}
	for(int i=1;i<=cnt;i++)
	{
		//for(int j=1;j<=c;j++) Num[i][j]=Num[i-1][j];
		//上面被注释的代码是用前缀和优化的代码 
		//但是由于acwing的内存限制得太死
		//导致只能用蓝书上的二分
		//复杂度多了一个块长
		//必须要选取合适的块长 
		for(int j=L[i];j<=R[i];j++)
		{
			//Num[i][a[j]]++;
			belong[j]=i;
		}
	}
	for(int i=1;i<=cnt;i++)
	{
		int res=0,tot=0;
		//res表示目前为止从i到j出现了奇数次的数字的个数
		//tot表示目前为止从i到j一个出现了多少种数字 
		for(int j=i;j<=cnt;j++)
		{
			for(int k=L[j];k<=R[j];k++)
			{
				if(num[a[k]]^1) res++;
				else res--;
				num[a[k]]^=1;
				if(!mark[a[k]])
				{
					tot++;
					mark[a[k]]=1;
				}
			}
			f[i][j]=tot-res;
		}
		for(int j=i;j<=cnt;j++)
		for(int k=L[j];k<=R[j];k++) num[a[k]]=mark[a[k]]=0;
		//注意这种还原技巧学会
		//没必要上memset 
	}
	int ans=0;
	for(int i=1;i<=m;i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l=(l+ans)%n+1,r=(r+ans)%n+1;
		if(l>r) swap(l,r);
		if(belong[l]==belong[r])
		{
			for(int j=l;j<=r;j++)
			Count[a[j]]++;
			int res=0;
			for(int j=l;j<=r;j++)
			if((Count[a[j]]&1)==0&&!mark[a[j]])
			{
				mark[a[j]]=1;
				res++;
			}
			printf("%d\n",res);
			ans=res;
			for(int j=l;j<=r;j++)
			{
				Count[a[j]]--;
				mark[a[j]]=0;
			}
		}
		else
		{
			int p=belong[l],q=belong[r];
			int res=0;
			if(p!=q-1) res+=f[p+1][q-1];
			int x=0,y=0;
			for(int j=l;j<=R[p];j++)
			if(!mark[a[j]])
			{
				//下面如果有前缀和优化
				//就可以少一个循环
				//块长就可以取根号n 
				mark[a[j]]=1;
				for(int k=j;k<=R[p];k++)
				if(a[k]==a[j]) x++;
				for(int k=L[q];k<=r;k++)
				if(a[k]==a[j]) x++;
				y=getnum(L[p+1],R[q-1],a[j]);//蓝书的做法 
				if((y&1)&&(x&1)) res++;
				else if(y&&(!(y&1))&&(x&1)) res--;
				else if(!y&&(!(x&1))) res++;//一定要考虑这个数字在整块中一次都没出现的情况 
				x=y=0;
			}
			for(int j=L[q];j<=r;j++)
			if(!mark[a[j]])
			{
				mark[a[j]]=1;
				for(int k=j;k<=r;k++)
				if(a[k]==a[j]) x++;
				y=getnum(L[p+1],R[q-1],a[j]);
				if((y&1)&&(x&1)) res++;
				else if(y&&(!(y&1))&&(x&1)) res--;
				else if(!y&&(!(x&1))) res++;
				x=y=0;
			}
			printf("%d\n",res);
			ans=res;
			for(int j=l;j<=R[p];j++) mark[a[j]]=0;
			for(int k=L[q];k<=r;k++) mark[a[k]]=0;
		}
	}
	return 0;
}
posted @ 2023-11-26 18:10  最爱丁珰  阅读(1)  评论(0编辑  收藏  举报