0
点赞
收藏
分享

微信扫一扫

2021-2022-2 ACM集训队每周程序设计竞赛(1) - 问题 F: 异或的和 - 题解

桑二小姐 2022-03-11 阅读 22
算法

题意:

将所给的 n n n 个数分为两个非空的集合 A A A B B B,要求满足: A A A中所有元素的异或值 + + + B B B中所有元素的异或值,最后得到的和的值最大。

思路:

一堆数的异或值,不难想到线性基,难点是要怎么用。

第一个比较难想的点是,我们需要先求出来所有数的异或和 s u m sum sum,或者换句话说,我们需要知道这 n n n 个数转换到个二进制位之后,每一个二进制位 1 1 1 的个数。
假设,在某一个二进制位, ( s u m > > i ) & 1 (sum>>i )\&1 (sum>>i)&1 的值为 1 1 1,这意味着无论怎么分组,两个集合的异或值 s u m A sumA sumA s u m B sumB sumB 在这一个二进制位上必定是一个 0 0 0 和一个 1 1 1,而在最终得到的值 a n s ans ans 中,这一个二进制位上的值也就必定是 1 1 1

而在 ( s u m > > i ) & 1 (sum>>i )\&1 (sum>>i)&1 的值为 0 0 0 的二进制位上,我们当然希望 s u m A sumA sumA s u m B sumB sumB 在这一位上均为 1 1 1,这样可以使最终值更大。那么就从高位到低位遍历,然后贪心即可。

这里还有一个细节,去除 ( s u m > > i ) & 1 (sum>>i )\&1 (sum>>i)&1 的值为 1 1 1的二进制位的影响之后,因为 s u m A ⊕ s u m B = s u m sumA\oplus sumB=sum sumAsumB=sum,然后 s u m A sumA sumA s u m B sumB sumB 每一个二进制位不是都为 0 0 0 就都为 1 1 1,也就是说, s u m A = s u m B sumA=sumB sumA=sumB (这里说的单指去掉值为 1 1 1 的二进制位之后的值),那么只需要贪心求出线性基中任意元素异或的最大值即可。
注意,这里所有的操作都建立在去除 ( s u m > > i ) & 1 (sum>>i )\&1 (sum>>i)&1 的值为 1 1 1的二进制位的影响之后,因此原数组中的数在插入线性基之前,必须去除相应二进制位上的值。

时间复杂度: O ( n ) O(n) On

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, M = 350, mod = 1e9 + 7;
#define LL long long
int n;
LL s[N];
LL p[110];

void add(LL x) {
	for (int i = 62; i >= 0; i--) {
		if (!(x >> i))
			continue;
		if (!p[i]) {
			p[i] = x;
			break;
		}
		x ^= p[i];
	}
	return ;
}

signed main() {

	cin >> n;
	LL sum = 0;
	for (int i = 1; i <= n; i++)
		scanf("%lld", &s[i]), sum ^= s[i];

	for (int i = 62; i >= 0; i--)
		if (sum >> i & 1ll) {
			for (int j = 1; j <= n; j++)
				if (s[j] >> i & 1ll)
					s[j] ^= (1ll << i);
		}

	for (int i = 1; i <= n; i++)
		add(s[i]);

	LL ans = 0;
	for (int i = 62; i >= 0; i--)
		if ((ans ^ p[i]) > ans)
			ans ^= p[i];
	cout << (ans ^ sum) + ans;
	return 0;
}
举报

相关推荐

0 条评论