HDU3949 XOR(线性基)

题目:

给你nn个数,从其中随便取任意数问你第kk小的异或和是多少。

题解:

这题是线性基的应用之一。我们知道一个集合的线性基可以异或出这个集合的所有异或和,并且方法唯一。对于一个数x能否被异或出来,我们可以这样做,假设x的最高位为r,那么在线性基里面找到最高为也为r的数,让x异或r。然后不断重复这个操作,如果最后x能为0那么肯定是能的。然后我们现在想想怎么通过线性基求第k小的异或和。
首先,线性基可以看成一个最大线性无关组,也就是说最高位以上都是0。现在假设最大线性基是这样的:

00100010 - - - - - - 4
00011000 - - - - - - 3
00000110 - - - - - - 2
00000001 - - - - - - 1

那么异或和排序显然是(用编号表示):
12(21)3(31)(312)......(4123)1\Rightarrow2\Rightarrow(2\bigoplus1)\Rightarrow3\Rightarrow(3\bigoplus1)\Rightarrow(3\bigoplus1\bigoplus2)\Rightarrow......\Rightarrow(4\bigoplus1\bigoplus2\bigoplus3)
这个排序显然是按照二进制最高位排序加上去的。但是如果想按这样的规律数显然是不行的。不过,我们可以联系下上面说的一个数x能否组成的原理。如果我将线性基用离散化的思想处理,然后再按照上面的排序思想。就可以把题目转化乘k能否由离散后的线性基组成。
举个例子:我们假设要求第k小的数,先上面的线性基离散化就可以看成

1000
0100
0010
0001

然后,这个新的线性基可以组成2412^4-1排名内的所有数。也就是原线性基所有可以组成的异或的数量。k一定在这里面,否则就不存在。因此,我们只需要分解k的所有1就行了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
long long a[maxn],p[maxn],tot;
void Guass(int n)
{
	memset(p,0,sizeof(p));
	for(int i=1;i<=n;i++)
	{
		for(int j=63;j>=0;j--)
		{
			if((a[i]>>j)&1)
			{
				if(p[j]) a[i]^=p[j];
				else
				{
					p[j]=a[i];
					break;
				}
			}
		}
	}
	for(int i=63;i>=0;i--)
	{
		if(!p[i])continue;
		for(int j=i+1;j<=62;j++)
		{
			if((p[j]>>i)&1) p[j]^=p[i];
		}
	}
	tot=0;
	for(int i=0;i<=63;i++) if(p[i]) p[tot++]=p[i];
}
int main()
{
	int T,i,j,n,Q;
	long long k;
	scanf("%d",&T);
	for(int s=1;s<=T;s++)
	{
		printf("Case #%d:\n",s);
		scanf("%d",&n);
		for(i=1;i<=n;i++) scanf("%I64d",&a[i]);
		Guass(n);
		scanf("%d",&Q);
		while(Q--)
		{
			scanf("%I64d",&k);
			if(n!=tot)k--;
			if(k>=(1ll<<tot))printf("-1\n");
			else
			{
				long long ans=0;
				for(i=0;i<=63;i++) if((k>>i)&1) ans^=p[i];
				printf("%I64d\n",ans);
			 } 
		}
	}
}

Q.E.D.