异或序列

题目描述

已知一个长度为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。

首先异或和满足前缀,也就是说设val[i]为a[1]^a[2]^...^a[i],那么a[i]^a[i+1]^...^a[j]=val[j]^val[i-1]

  而且异或不仅满足交换律,而且对于a^b=c时,a^c=b,b^c=a这两个式子同样成立

  那么就好做了,假设当前i到j这个子串的异或和为k,就说明val[j]^val[i-1]=k,也就是val[i-1]^k=val[j],val[j]^k=val[i-1]

  然后在区间转移的时候,设flagi]为当前区间值为i的前缀有多少个,然后对于增加序列长度的操作,假设新加的位置为r+1,我们先将flag[val[r+1]]++,然后求出ans+=flag[val[r+1]^k],左边扩展也是如此,不过注意,向左扩展时,对ans的更新是用val[l-1]的,因为是val[j]与val[i-1]可以满足前缀

  而且向右扩展的时候,如果val[r+1]^k=val[l-1]的话,ans++,因为我们更新的时候没有计算[l...r+1]区间的影响,所以要维护一下

  而对于区间缩小的情况,就ans先减,再更新flag,因为要先消除贡献再减flag,其它步骤类似就好了

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,k,block;
int val[100050],part[100050];
int ans,flag[100050];
struct node
{
    int l,r,id;
    int ans;
}query[100050];
bool cmp1(node x,node y)
{
    if(part[x.l]==part[y.l])
    {
        return x.r<y.r;
    }
    return x.l<y.l;
}
bool cmp2(node x,node y)
{
    return x.id<y.id;
}
void update(int pos,int op)
{
    if(!op)
    {
        flag[val[pos]]--;
        ans-=flag[val[pos]^k];
    }
    else
    {
        ans+=flag[val[pos]^k];
        flag[val[pos]]++;
    }
}
void fun()
{
    int i,l=1,r=0;
    flag[0]=1;
    for(i=1;i<=m;i++)
    {
        while(r<query[i].r)
        {
            update(++r,1);
        }
        while(r>query[i].r)
        {
            update(r--,0);
        }
        while(l<query[i].l)
        {
            update(l-1,0);
            l++;
        }
        while(l>query[i].l)
        {
            l--;
            update(l-1,1);
        }
        query[i].ans=ans;
    }
}
int main()
{
    scanf("%d %d %d",&n,&m,&k);
    block=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&val[i]);
        val[i]^=val[i-1];
        part[i]=(i-1)/block;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&query[i].l,&query[i].r);
        query[i].id=i;
    }
    sort(query+1,query+1+m,cmp1);
    fun();
    sort(query+1,query+1+m,cmp2);
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",query[i].ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值