CF703D Mishka and Interesting sum
CF703D Mishka and Interesting sum
- 给定nn 个数的序列 aa。mm 次操作。操作有一种:
l r
:求 a_l\sim a_ra**l∼a**r 中,出现偶数次的数的异或和。
- 1\le n,m\le 10^61≤n,m≤106,1\le a_i\le 10^91≤a**i≤109。
题解:
解决本题的关键是牢记异或的一个性质:异或一个数两次相当于异或了个寂寞。
那么我们只需要想办法把一个区间出现奇数次的东西全搞没就好。就利用这个性质。
我们先尝试着把整个区间的数都异或上去。发现,偶数次的数已经都异或成寂寞了,而奇数次的数各自保留了一个作为异或和的贡献。
芜湖?那我们再把偶数次的数变成奇数次,奇数次的数还是奇数次,再异或上去,不就把奇数次的数都异或成寂寞,把偶数次的数都留下了一个么?
思路豁然开朗:答案就是区间异或和异或上区间所含有的数(每个数只异或一次)的异或和。
区间异或和可以用异或的可差分性,用异或前缀和先处理好。
那么区间所含有的数,每个数只异或一次,该怎么处理呢?
用树状数组维护。还是抓住”异或一个数两次相当于异或了个寂寞“这个性质。只需要把这个数之前重复出现的数再异或一次,就相当于只保留了一个。但是这样的修改是有后效性的,也就是说,如果我们先处理后面的询问区间,就会提前把前面的区间修改掉,导致之后处理前面的区间时出错。于是我们把所有询问离线处理。维护pre数组表示这个数前面第一个和这个数一样的数的位置,就可以完成这一任务了。
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e6+6;
int n,m;
int a[maxn],b[maxn],sum[maxn];
int head[maxn],pre[maxn];
int ans[maxn];
int tree[maxn];
struct node
{
int l,r,id;
}q[maxn];
bool cmp(node a,node b)
{
if(a.r==b.r)
return a.l<b.l;
return a.r<b.r;
}
void update(int x,int k)
{
for(int i=x;i<=n;i+=i&-i)
tree[i]^=k;
}
int query(int x)
{
int ret=0;
for(int i=x;i;i-=i&-i)
ret^=tree[i];
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
int size=unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+size+1,a[i])-b;
for(int i=1;i<=n;i++)
{
pre[i]=head[a[i]];
head[a[i]]=i;
sum[i]=sum[i-1]^b[a[i]];
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int pos=1;
for(int i=1;i<=m;i++)
{
int l=q[i].l,r=q[i].r;
while(pos<=r)
{
if(pre[pos])
update(pre[pos],b[a[pos]]);
update(pos,b[a[pos]]);
pos++;
}
ans[q[i].id]=query(q[i].r)^query(q[i].l-1)^sum[r]^sum[l-1];
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}