CF703D Mishka and Interesting sum

CF703D Mishka and Interesting sum

洛谷传送门

  • 给定nn 个数的序列 aa。mm 次操作。操作有一种:
    1. l r:求 a_lsim a_ra**la**r 中,出现偶数次的数的异或和。
  • 1le n,mle 10^61≤n,m≤106,1le a_ile 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
",ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/fusiwei/p/14032527.html