题解 P4462 【[CQOI2018]异或序列】
莫队
异或,真是个神东西!!!
首先异或和满足前缀,也就是说设sum[i]为a[1]^a[2]^...^a[i],那么a[i]^a[i+1]^...^a[j]=sum[j]^sum[i-1]
而且异或不仅满足交换律,而且对于a^b=c时,a^c=b,b^c=a这两个式子同样成立
那么就好做了,假设当前i到j这个子串的异或和为k,就说明sum[j]^sum[i-1]=k,也就是sum[i-1]^k=sum[j],sum[j]^k=sum[i-1]
然后在区间转移的时候,设cnt[i]为当前区间值为i的前缀有多少个,然后对于增加序列长度的操作,假设新加的位置为r+1,我们先将cnt[sum[r+1]]++,然后求出ans+=cnt[sum[r+1]^k],左边扩展也是如此,不过注意,向左扩展时,对ans的更新是用sum[l-1]的,因为是sum[j]与sum[i-1]可以满足前缀
而且向右扩展的时候,如果sum[r+1]^k=sum[l-1]的话,ans++,因为我们更新的时候没有计算[l...r+1]区间的影响,所以要维护一下
而对于区间缩小的情况,就ans先减,再更新cnt,因为要先消除贡献再减cnt,其它步骤类似就好了
最后黏上代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define MA 100005
using namespace std;
int n,m,k,pos[MA];
int c[MA],sum[MA],cnt[MA<<1];
struct data{
int l,r,num;
int res;
}a[MA];
inline bool cmp(const data &x,const data &y){
if(pos[x.l]==pos[y.l]) return x.r<y.r;
return x.l<y.l;
}
inline bool cmp2(const data &x,const data &y){
return x.num<y.num;
}
inline void motm(){
int i,j;
int l=1,r=0,ans=0;
for(i=1;i<=m;i++){
for(;r<a[i].r;){
r++;
cnt[sum[r]]++;
ans+=cnt[sum[r]^k];
if((sum[r]^k)==sum[l-1]) ans++;
}
for(;r>a[i].r;){
ans-=cnt[sum[r]^k];
cnt[sum[r]]--;
if((sum[r]^k)==sum[l-1]) ans--;
r--;
}
for(;l<a[i].l;){
ans-=cnt[sum[l-1]^k];
cnt[sum[l]]--;
l++;
}
for(;l>a[i].l;){
l--;
cnt[sum[l]]++;
ans+=cnt[sum[l-1]^k];
}
a[i].res=ans;
}
return ;
}
int main(){
int i;
scanf("%d %d %d",&n,&m,&k);
int blk=(int)(sqrt(n));
for(i=1;i<=n;i++){
scanf("%d",&c[i]);
sum[i]=sum[i-1]^c[i];
pos[i]=(i-1)/blk+1;
}
for(i=1;i<=m;i++){
scanf("%d %d",&a[i].l,&a[i].r);
a[i].num=i;
}
sort(a+1,a+m+1,cmp);
motm();
sort(a+1,a+n+1,cmp2);
for(i=1;i<=m;i++){
printf("%d\n",a[i].res);
}
return 0;
}
本人蒟蒻,不喜勿喷