题目链接:Problem - 617E - Codeforces
题目翻译:已知一个长度为 n 的整数数列 a[1],a[2],…,a[n] ,给定查询参数 l、r ,问在 [l,r] 区间内,有多少连续子段满足异或和等于 k 。也就是说,对于所有的 x,y (l≤x≤y≤r),能够满足a[x]^a[x+1]^…^a[y]=k的x,y有多少组。
这是一个符合莫队性质的题目,还是老样子,模板我就不多做说明了,我主要说一下add和sub函数的思考过程以及易错点。
设sumxor[x]=a[1]^a[2]^……^a[x]
容易知道:a[x]^a[x+1]^…^a[y]=sumxor[y]-sumxor[x-1],所以关于连续几个数的异或值我们可以转化为他们的区间端点异或值来求。
剩下我们就考虑两个点的异或值即可:
我们先考虑add函数,假如我们要加入的点的值为x,我们看看这样会对结果造成什么影响,首先原来异或值就已经等于k的连续区间肯定不会收到影响,原来前缀异或和为k^x的位置,如果与我们新加的点进行异或就可以得到一段异或值为k的区间,所以我们要加上当前区间中前缀异或和为k^x的位置的数目,最后别忘了对异或值x的出现次数+1。
下面我们考虑sub函数,假如我们要删除的点的值为x,与add函数相同我们应该找不包括这个点以外的点的前缀异或值等于k^x的数出现的次数,所以我们应该先对异或值为x的cnt数组减一,再减去在当前区间中出现的前缀异或值等于k^x的数出现的次数,就可以找到答案。
最后一个需要注意的就是:我们的当前区间如果是[ l , r ],那么我们cnt值记录的应该是[ l -1 , r ]的前缀异或值。
下面上代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1<<20;
ll a[N],sum[N],cnt[N],ans;
ll n,m,k;
struct node{
ll l,r,pl,id;
}p[N];
bool cmp(node a,node b)
{
if(a.pl==b.pl) return a.r<b.r;
return a.pl<b.pl;
}
//x只能影响包括x的连续区间,x可能是区间开头也可能是区间结尾
void add(ll x)
{
ans+=cnt[x^k];
//之所以后进行cnt++是因为要排除当前待加入值的影响
cnt[x]++;
}
void sub(ll x)
{
cnt[x]--;//先排除待删除值的影响
ans-=cnt[x^k];
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&k);
int pl=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
a[i]^=a[i-1];
}
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&p[i].l,&p[i].r);
p[i].id=i;
p[i].l--;
p[i].pl=p[i].l/pl;
}
sort(p+1,p+m+1,cmp);
ll l=1,r=0;
for(int i=1;i<=m;i++)
{
while(l<p[i].l)
{
sub(a[l]);
l++;
}
while(l>p[i].l)
{
l--;
add(a[l]);
}
while(r<p[i].r)
{
r++;
add(a[r]);
}
while(r>p[i].r)
{
sub(a[r]);
r--;
}
sum[p[i].id]=ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",sum[i]);
return 0;
}