作诗
首先将原区间分块(设块的大小是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;
}