upc 6759 异或序列 【莫对算法+前缀异或】

题目描述

已知一个长度为n的整数数列a1,a2,…,an,给定查询参数l、r,问在al,al+1,…,ar区间内,有多少子序列满足异或和等于k。也就是说,对于所有的x,y(l≤x≤y≤r),满足ax⊕ax+1⊕⋯⊕ay=k的x,y有多少组。

 

输入

输入第一行为3个整数n,m,k。第二行为空格分开的n个整数,即a1,a2,…,an。接下来m行,每行两个整数lj,rj,代表一次查询。

 

输出

输出共m行,对应每个查询的计算结果。

 

样例输入

4 5 1
1 2 3 1
1 4
1 3
2 3
2 4
4 4

 

样例输出

4
2
1
2
1

 

提示

对于30%的数据,1≤n,m≤1000。
对于100%的数据,1≤n,m≤105,0≤k,ai≤105,1≤lj≤rj≤n。

 

[提交][状态]

 

【小结】

这道题算是裸的莫队算法了,我们只需要求一个前缀异或就可以了。需要用到异或的一些性质:

如果区间 [i,j] 的异或值为k,则 sum[j]^sum[i-1]=k ; sum[i-1]^k=sum[j]; sum[j]^k=sum[i-1] 

当区间移动时 如果右端点加了一个值t ,sum[t]=x我们是知道的 所以整个区间的答案增加了 cnt[x^k]个

因为 x^x^k=k;

需要注意到 因为判断的时候始终要用到左端点的前一个值 所以我们输入时直接让l–;

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+3;
int a[maxn];
struct node
{
    int l,r,id;
}P[maxn];
int res=0;
int k;
int ans[maxn],cnt[maxn];//cnt[i]表示区间中前缀异或值为i的序列个数
int block;
int cmp(node x,node y)
{
    if(x.l/block==y.l/block)//同一块中说明l很相近
        return x.r<y.r;
    else//l差距很大
        return x.l<y.l;
}
int dw[maxn];
void update(int x)
{
    res+=cnt[x^k];
    cnt[x]++;
}
void deletes(int x)
{
    res-=cnt[x^k];
    cnt[x]--;
}
int main()
{
    int n,q;//n个元素 q次询问
    cin>>n>>q>>k;
    for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            dw[i]=dw[i-1]^a[i];
        }
    for(int i=1;i<=q;i++)
    {
        cin>>P[i].l>>P[i].r;
        P[i].id=i;
        P[i].l--;
    }
    block=(int)sqrt(n);
    sort(P+1,P+q+1,cmp);
    int L=1,R=0;
    for(int i=1;i<=q;i++)
    {
        while(L<P[i].l)deletes(dw[L++]);
        while(L>P[i].l)update(dw[--L]);
        while(R<P[i].r)update(dw[++R]);
        while(R>P[i].r)deletes(dw[R--]);
        ans[P[i].id]=res;
    }
    for(int i=1;i<=q;i++)
        printf("%d\n",ans[i]);
    return 0;
}

 

 

 

点赞