Codeforces 707D

这篇博客介绍了如何利用线段树和树状数组解决区间内出现次数为偶数的数的异或和查询问题。通过离线处理和维护区间内的不同数字异或和,可以高效地回答这类询问。文章提供了两种实现方式,一种是直接维护异或和,另一种是结合数组的离散化。每种方法都遵循先按右端点排序询问,然后在线处理区间更新的原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一个长n的数组,m个询问

每个询问给l和r,询问数组区间[l,r]中出现次数为偶数的数的异或和。

我们先从异或计算的方面思考,a \, xor\,a\,xor\,a=a,也就是奇数次的异或本身还是本身,同时也有偶数次异或本身是0的结论

那么我们想,在线段树维护区间上能否利用这一性质,维护出某种偶数次异或还是本身的效果呢,那么在计算前我们先把答案设成区间内出现过的数的异或和(出现了只算一次,多次只算一次),假如说区间的数为2,2,答案应该是2,但是这个区间总异或是0,我们在计算前把答案设置成出现过的数的异或,也就是2,三次异或得到2。答案就是区间总异或和 异或上 出现的数的异或和(出现的数多少次都只算一次),简而言之就是让答案异或上出现过的所有不同数字,使原来异或本身次数为奇数的变为偶数,异或和是0,消除贡献;使异或本身次数是偶数的变为奇数,异或和就是其本身,最后答案就是所有出现次数为偶数的数的异或和

同时区间总异或和还可以用异或版本的前缀和优化。

那么要点就在计算给定区间出现的不同的数的异或和。

先考虑一个问题,给定一个数组,同样的询问,计算区间有多少不同的数字?

题目链接:P1972 [SDOI2009] HH的项链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

我们考虑离线处理,按照询问的右端点顺序依次加入数组的值,先按照右端点排序询问,按顺序考虑询问,每次右移询问的右端点时考虑移动区间的数,对于每个数,如果有多次出现,应该靠右记录贡献,例如:

此时这样的查询就记录不到最右的3了

每次查询都是区间求和,缩短时间用树状数组或者线段树都可以。

所以洛谷这题的代码 :

#include<bits/stdc++.h>
using namespace std;

int read()
{
	int ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<3)+(ret<<1)+ch-48;
		ch=getchar();
	}
	return ret*base;
}


inline int lowbit(int x){return -x&x;}

struct question
{
	int l,r,id;
	bool operator<(const question &a) const {return r<a.r;}
}q[1000005];

int n,m,ans[1000005],a[1000005],kind[1000005],last[1000005],tree[1000005];

void add(int x,int k)
{
	while(x<=n)
	{
		tree[x]+=k;
		x+=lowbit(x);
	}
}

int getsum(int x)
{
	int ret=0;
	while(x)
	{
		ret+=tree[x];
		x-=lowbit(x);
	}
	return ret;
}

int query(int l,int r)
{
	return getsum(r)-getsum(l-1);
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	m=read();
	for(int i=1;i<=m;i++) q[i]=(question){read(),read(),i};
	sort(q+1,q+1+m);//反复贡献到最右边
	int r=0;
	for(int i=1;i<=m;i++)
	{
		while(r<q[i].r)
		{
			r++;
			if(last[a[r]]) add(last[a[r]],-1);
			add(r,1);
			last[a[r]]=r;
		}
		ans[q[i].id]=query(q[i].l,q[i].r);
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
 	return 0;
}

CF这题因为数组数字太大,不能直接用数组记录上一次出现的位置,所以需要离散化,并且此时树状数组维护的数组计算不是加法,而是异或。

#include<bits/stdc++.h>
using namespace std;

int read()
{
	int ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<3)+(ret<<1)+ch-48;
		ch=getchar();
	}
	return ret*base;
}


inline int lowbit(int x){return -x&x;}

struct question
{
	int l,r,id;
	bool operator<(const question &a) const {return r<a.r;}
}q[1000005];

int n,m,ans[1000005],a[1000005],kind[1000005],v[1000005];
int b[1000005],sum_xor[1000005],last[1000005],tree[1000005];

void add(int x,int k)
{
	while(x<=n)
	{
		tree[x]^=k;
		x+=lowbit(x);
	}
}

int getsum(int x)
{
	int ret=0;
	while(x)
	{
		ret^=tree[x];
		x-=lowbit(x);
	}
	return ret;
}

int query(int l,int r)
{
	return getsum(r)^getsum(l-1);
}

int main()
{
	n=read();//出现次数是偶数的数 == 不同种类的数 xor 出现奇数次数的数
	for(int i=1;i<=n;i++) sum_xor[i]=sum_xor[i-1]^(a[i]=b[i]=read());
	sort(b+1,b+1+n);
	for(int i=1;i<=n;i++)
	{
		int pos=lower_bound(b+1,b+1+n,a[i])-b;
		v[pos]=a[i];a[i]=pos;
	}
	m=read();
	for(int i=1;i<=m;i++) q[i]=(question){read(),read(),i};
	sort(q+1,q+1+m);
	for(int i=1,r=0;i<=m;i++)
	{
		while(r<q[i].r)
		{
			r++;
			if(last[a[r]]) add(last[a[r]],v[a[r]]);
			add(r,v[a[r]]);
			last[a[r]]=r;
		}
		ans[q[i].id]=query(q[i].l,q[i].r)^(sum_xor[q[i].r]^sum_xor[q[i].l-1]);
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
 	return 0;
}

这种方法求区间不同元素的问题一般都是保存询问,离线回答,而回答的时候,是按照区间右端点顺序在线回答的,是一种半在线的做法。

另外,这题的思路也可以这样考虑

对于区间里的数分两种:出现次数为偶数,出现次数为奇数,他们总体的集合就是这个区间的所有不同数,因此 出现次数为偶数的所有数的异或和 异或 出现次数为奇数的所有数的异或和 = 出现的所有不同的数的异或和

移项就是 出现次数为偶数的所有数的异或和  = 出现的所有不同的数的异或和 异或 出现次数为奇数的所有数的异或和

师兄这个思路更简洁一点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值