【HDU3949】XOR 线性基

博客介绍了如何解决一道名为「HDU3949」的编程题,题目要求计算一系列数的异或和,并找出第k大的异或和。线性基在此问题中起到关键作用,通过将k的二进制形式与线性基进行异或操作来获取答案。博主指出了最初错误的实现方式会导致WA(Wrong Answer),并解释了错误的原因在于不正确的线性基维护策略,可能导致高位1丢失,进而导致计算错误。最终,博主提供了AC(Accepted)代码。

#include <stdio.h>
int main()
{
	puts("转载请注明出处谢谢");
	puts("http://blog.youkuaiyun.com/vmurder/article/details/43448493");
}


题意:给若干个数让你异或,然后询问第k大的异或和。


题解:

先搞出来线性基,然后第k大的异或和就是:

把k二进制拆分,第i位上有1,就把第i个线性基异或进来。

原因:

因为线性基是一堆高位上的1(或许有一些位动不了),然后把这样每一位可以填0/1,跟二进制差不多。

自己脑补去吧。


……我在说什么啊,我明白但是懒得写了。别管了,扒代码或者留言神马的吧。


经验之谈:

最开始写的是这份代码(WA):

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 10100
using namespace std;
int n,m;
unsigned long long a[N],ins[70];
bool flag;
void EX_Ins(int n)
{
	int i,j,k;
	flag=0;
	memset(ins,0,sizeof ins);
	for(i=n;i;i--)
	{
		for(j=63;~j;j--)if((a[i]>>j)&1)
		{
			if(!ins[j])
			{
				ins[j]=a[i];
				for(k=63;k>j;k--)
					if((ins[k]>>j)&1)
						ins[k]^=ins[j];
				break;
			}
			else a[i]^=ins[j];
		}
		if(!a[i])flag=1;
	}
	return ;
}
int main()
{
	freopen("test.in","r",stdin);
	int i,g,_g;
	unsigned long long k;
	for(scanf("%d",&_g),g=1;g<=_g;g++)
	{
		printf("Case #%d:\n",g);
		scanf("%d",&n);
		for(i=1;i<=n;i++)scanf("%llu",&a[i]);
		sort(a+1,a+n+1);
		EX_Ins(n);
		for(m=i=0;i<=63;i++)
		{
			if(ins[i])ins[m++]=ins[i];
		}
		for(scanf("%d",&n);n--;)
		{
			scanf("%llu",&k);
			if(flag)k--;
			if(k>>m)puts("-1");
			else {
				unsigned long long ret=0;
				for(i=0;i<m;i++)if((k>>i)&1)
					ret^=ins[i];
				printf("%llu\n",ret);
			}
		}
	}
	return 0;
}

它WA了,请看这组数据:

1
3
77 89 53 
1
7

为什么会WA呢?

我维护线性基的方法是先排个序从大到小往里面加,

然后这样出来一个线性基时就再对之前的线性基进行修改、、

然后就WA了。


貌似有理有据,但是错在了哪里呢?


嗯,一个大的数A可能被之前某个线性基异或一下,变成比之后的数更小的数了,

于是它的最高位上有个1,

然后之后某个初始值小的数B成为了线性基,但是它在数A那个线性基的那一位上有1,

然后自然就挂了~~


询问时就会有3<2的情况了(ins[1]^ins[2]<ins[2])

诶,233。


AC代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 10100
using namespace std;
int n,m;
unsigned long long a[N],ins[70];
bool flag;
void EX_Ins(int n)
{
	int i,j,k;
	flag=0;
	memset(ins,0,sizeof ins);
	for(i=n;i;i--)
	{
		for(j=63;~j;j--)if((a[i]>>j)&1)
		{
			if(!ins[j])
			{
				ins[j]=a[i];
				for(k=0;k<63;k++)
					for(int r=k+1;r<=63;r++)
						if((ins[r]>>k)&1)
							ins[r]^=ins[k];
				break;
			}
			else a[i]^=ins[j];
		}
		if(!a[i])flag=1;
	}
	return ;
}
int main()
{
	freopen("test.in","r",stdin);
	int i,g,_g;
	unsigned long long k;
	for(scanf("%d",&_g),g=1;g<=_g;g++)
	{
		printf("Case #%d:\n",g);
		scanf("%d",&n);
		for(i=1;i<=n;i++)scanf("%llu",&a[i]);
	//	sort(a+1,a+n+1);
		EX_Ins(n);
		for(m=i=0;i<=63;i++)
		{
			if(ins[i])ins[m++]=ins[i];
		}
		for(scanf("%d",&n);n--;)
		{
			scanf("%llu",&k);
			if(flag)k--;
			if(k>>m)puts("-1");
			else {
				unsigned long long ret=0;
				for(i=0;i<m;i++)if((k>>i)&1)
					ret^=ins[i];
				printf("%llu\n",ret);
			}
		}
	}
	return 0;
}


HDU3949 是一个与线性基XOR Basis)相关的编程问题,主要涉及异或运算和高斯消元法。该问题要求在给定的一组整数中,找出第 $ k $ 小的异或值(排除 0),这需要通过构建线性基并进行进一步处理来实现。 ### 解题思路 #### 1. 线性基的构建 线性基是解决异或问题的重要工具,它能够表示所有输入元素的异或组合。构建线性基的核心思想是使用类似于高斯消元法的策略,从高位到低位逐步处理每个数,确保线性基中的每个元素都对应一个唯一的二进制位。 构建线性基的具体步骤如下: - 从高位(如 62 位,因为输入的数最大为 $ 10^{18} $)到低位依次处理。 - 对于每一位 $ i $,检查当前数组中是否存在一个数的第 $ i $ 位为 1。 - 如果存在,则将其交换到当前处理位置,并用该数对数组中其他数进行异或操作,以消除该位的 1。 #### 2. 线性基的应用 构建完线性基后,可以通过线性基生成所有可能的异或值。假设线性基中有 $ r $ 个非零元素,则可以生成 $ 2^r - 1 $ 个不同的异或值(排除 0)。 为了找出第 $ k $ 小的异或值,需要将线性基进行进一步处理: -线性基中的元素进行调整,使其满足“唯一性”:每个元素的最高位是唯一的,并且该位以下的位均为 0。 - 通过位运算,将 $ k $ 转换为二进制形式,并根据每一位是否为 1 来决定是否异或对应的线性基元素。 #### 3. 处理特殊情况 - 如果输入的数中可以异或出 0,则需要调整 $ k $ 的值(例如,当 $ k = 1 $ 时,结果可能为 0)。 - 如果 $ k $ 超过了所有可能的异或值的数量,则输出 -1。 ### 代码示例 以下是一个基于上述思路的代码实现: ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAX_BIT = 62; // 最大位数 const int MAX_N = 10010; // 最大输入数量 ll basis[MAX_BIT + 1]; // 线性基数组 // 插入一个数到线性基中 void insert(ll x) { for (int i = MAX_BIT; i >= 0; i--) { if (x & (1LL << i)) { if (!basis[i]) { basis[i] = x; break; } else { x ^= basis[i]; } } } } // 构建标准线性基 int buildStandardBasis() { int r = 0; for (int i = MAX_BIT; i >= 0; i--) { if (basis[i]) { for (int j = i - 1; j >= 0; j--) { if (basis[i] & (1LL << j)) { basis[i] ^= basis[j]; } } basis[r++] = basis[i]; } } return r; } int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); memset(basis, 0, sizeof(basis)); for (int i = 0; i < n; i++) { ll x; scanf("%lld", &x); insert(x); } // 构建标准线性基 int r = buildStandardBasis(); int Q; scanf("%d", &Q); while (Q--) { ll k; scanf("%lld", &k); if (r < n) k--; // 处理 0 的情况 if (k >= (1LL << r)) { printf("-1\n"); continue; } ll ans = 0; for (int i = 0; i < r; i++) { if (k & (1LL << i)) { ans ^= basis[i]; } } printf("%lld\n", ans); } } return 0; } ``` ### 4. 复杂度分析 - **时间复杂度**:构建线性基的时间复杂度为 $ O(n \cdot \log A) $,其中 $ A $ 是输入数的最大值;查询的时间复杂度为 $ O(Q \cdot \log A) $。 - **空间复杂度**:线性基占用 $ O(\log A) $ 的空间。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值