题目大意: 一个序列,m个询问,每次询问区间内有多少个出现过正偶数次的数。
考虑用分块做。
分块的基本套路就是块与块之间的信息可以合并,然而这题并没有什么可以合并的。
但是可以预处理答案呀!
- 表示第i块到第j块的答案
可以 预处理出 数组但是,对于一个询问,不但要处理中间的连续的块,还要处理两边剩下的呀。于是,对于两边零散的数字,记录下每种数字出现了几次,再搞出它在中间的连续的块中出现过几次然后更新一下就好了。
那么现在就只剩下一个小问题了: 怎么搞每一个数字在一段连续的块中的出现次数?
为了节省空间,我想到了一个很好的时间换空间的沙雕做法:记录下每种数字出现的位置,然后二分求解。算法总时间复杂度:
。
当然最简单的办法就是直接存下来了——利用前缀和思想, 表示数字 在1~i块的出现次数即可。
代码如下(用的是n sqrtn logn的做法,讲真,慢的一比,要加读优再吸口氧才能过……):
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 500010
int n,m;
int a[maxn];
struct node{int x,pos;};
node b[maxn];
int ll[2*maxn],rr[2*maxn];
int v[2*maxn];//记录每种数字的出现次数,下面会多次使用
int check(int x,int le,int ri)
{
if(le>ri||ll[x]==0)return v[x]%2==0;
int l=ll[x],r=rr[x];
int left=-1,right=-1;
while(l<=r)
{
int mid=l+r>>1;
if(b[mid].pos>=le&&b[mid].pos<=ri)left=mid,r=mid-1;
else if(b[mid].pos<le)l=mid+1;
else r=mid-1;
}
if(left==-1)return v[x]%2==0;//如果没出现过
l=ll[x],r=rr[x];
while(l<=r)
{
int mid=l+r>>1;
if(b[mid].pos>=le&&b[mid].pos<=ri)right=mid,l=mid+1;
else if(b[mid].pos<le)l=mid+1;
else r=mid-1;
}
//二分出x这个数字在le~ri区间内的出现位置在b数组中的区间
if((right-left+1)%2==1)return v[x]%2==1?1:0;//根据奇偶性修改ans
else return v[x]%2==1?-1:0;
}
bool cmp(node x,node y){return x.x==y.x?(x.pos<y.pos):x.x<y.x;}
int belong[maxn],l[1010],r[1010],cnt,block;
void work()
{
int tot=0;cnt=1;
block=__builtin_sqrt(n);
l[cnt]=1;
for(int i=1;i<=n;i++)
{
if(tot==block)
{
tot=0;
r[cnt]=i-1;
l[++cnt]=i;
}
tot++;
belong[i]=cnt;
}
r[belong[n]]=n;
}
int f[1010][1010];
int zhan[maxn],t=0;
void work2()
{
int ans=0;
for(int i=1;i<=cnt;i++)
{
for(int j=i;j<=cnt;j++)
{
for(int p=l[j];p<=r[j];p++)
{
v[a[p]]++;
if(v[a[p]]%2==0)ans++;
else if(v[a[p]]!=1)ans--;
else zhan[++t]=a[p];
}
f[i][j]=ans;
}
while(t>0)v[zhan[t--]]=0;
ans=0;
}
}
void go(int x,int y)
{
for(int i=x;i<=y;i++)
{
v[a[i]]++;
if(v[a[i]]==1)zhan[++t]=a[i];
}
}
inline char cn()//读优
{
static char buf[1000010],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
void read(int &x)
{
x=0;int f1=1;char ch=cn();
while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();}
while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn();
x*=f1;
}
int main()
{
int opa;//这个东西是废的
read(n);read(opa);read(m);
for(int i=1;i<=n;i++)
read(a[i]),b[i]=(node){a[i],i};//b数组是用来记录每种数字的出现位置的
sort(b+1,b+n+1,cmp);//值为第一关键字,在原序列的的位置为第二关键字
//排完序后,每一种数字就都排在一起了,并且他们的位置也都拍好了序
for(int i=1;i<=n;i++)
{
ll[b[i].x]=i;//记录下b[i].x这种数字出现的位置的区间的左端点
while(b[i].x==b[i+1].x)i++;
rr[b[i].x]=i;//记录右端点
}
work();//分块预处理
work2();//预处理答案(即f数组)
int lastans=0;
memset(v,0,sizeof(v));
while(m--)
{
int x,y;
read(x);read(y);
x=(x+lastans)%n+1;
y=(y+lastans)%n+1;
if(x>y)swap(x,y);
lastans=0;
if(belong[x]==belong[y])
{//暴力求解
for(int i=x;i<=y;i++)
{
v[a[i]]++;
if(v[a[i]]%2==0)lastans++;
else if(v[a[i]]!=1)lastans--;
}
printf("%d\n",lastans);
for(int i=x;i<=y;i++)
v[a[i]]--;//记得清空v数组
}
else
{
if(x!=l[belong[x]])go(x,r[belong[x]]),x=l[belong[x]+1];
if(y!=r[belong[y]])go(l[belong[y]],y),y=r[belong[y]-1];
//将两边零碎的元素放到栈里面
lastans=f[belong[x]][belong[y]];
for(int i=1;i<=t;i++)lastans+=check(zhan[i],x,y);
//处理每一个栈内的元素
while(t>0)v[zhan[t--]]=0;
printf("%d\n",lastans);
}
}
}