异或线性基
模型
问题:给定 个整数 ,求在这些数中选取任意个,使得他们的异或和最大。
首先有一个暴力的 DP 状态,令 表示前 个数能否凑出 ,然后转移,时间复杂度:,最后求答案。
考虑优化,令 为前 个数能凑出的数的集合,如果 在 中, 也在 中,那么显然 也在 中。反过来如果 在 中, 不在 中,那么 肯定不在 中,否则 就可以通过 得到。
如果 ,可以忽略 显然 。否则 中的每个数异或 后都是一个新元素。这样 对于 大小翻一倍,最多 次,能稍微优化这个算法,时间复杂度:。
但是 还是太大了,能不能压缩一下?要做到这一点,可以发现只有使得 大小翻倍的最多 个元素是重要的,其他的无关紧要, 就是这些重要元素 的子集异或和所组成的集合。
但是这样,却失去了查询 是否在 的能力,我需要找回它。
如果只直接存储这些元素很难做到这一点。但是我们想要的不是这些元素,而是这些元素的异或和。并且可以发现存储 和 的效果是一样的。推广一下,将存储的数字随意地进行异或操作得到的 都是不变的,因为通过异或操作能将操作后的序列又变回原序列,这个操作是可逆的。
有了这个操作,就可以判断了。可以采取一下策略:对于每个 ,我们让存储的集合内都只有至多一个数最高位为 ,令 表示最高位为 的这个数字,没有则为 。然后判断 是否在 时,从高到低枚举每一位 ,分以下两种情况:
- 在这一位为 ,如果 在 里,肯定异或和里不会有 ,不作任何操作。
- 否则,如果 ,即这一位没有数字,那么 不在 中,,退出。否则如果 在 里,肯定异或和里一定有 ,将 ,表示后面的数要凑出 ,同时保证了 在第 位为 。
求答案时,同样从大到小枚举 ,如果 这一位为 ,不做操作。否则 。
时间复杂度:。
void Insert(ll x) { 插入 x
for (int i = 49; i >= 0; i--) {
if ((x >> i) & 1) {
if (!visxor[i]) {
visxor[i] = x;
return ;
}
x ^= visxor[i];
}
}
}
for (int i = 49; i >= 0; i--) {
if (!((ans >> i) & 1) && visxor[i]) {
ans ^= visxor[i];
}
}
题目1:AT_abc141_f
题意
给定 个非负整数 ,将它们分成两组。记其中一组异或和为 ,令一组异或和为 ,求 的最大值。
思路
令 为 个数的异或和,确定了 ,那么 。用线性基求出可以凑出的数集,从高到低考虑第 位的贡献:
- 如果 第 位为 ,那么 这一位的取值无所谓了,无论如何都是 ,插入时将所有数第 位赋成 。
- 否则,尽可能让 的第 位为 ,成功就有 的贡献。如果 第 位本就是 ,不做操作就可以成功。否则如果 , ,成功,否则失败。
时间复杂度:。
#include <iostream>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5, MAXV = 60;
ll x, s, ans, s1, s2, a[MAXN], visxor[MAXV];
int n;
void Insert(ll x) {
for (int i = 59; i >= 0; i--) {
if ((x >> i) & 1) {
if (!visxor[i]) {
visxor[i] = x;
return ;
}
x ^= visxor[i];
}
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i], s ^= a[i];
}
for (int i = 1; i <= n; i++) {
a[i] &= ((1ll << 60) - 1) ^ s; // 将 s 为 0 的位赋为 0
Insert(a[i]);
}
for (int i = 59; i >= 0; i--) {
if (!((s >> i) & 1)) {
if (!((s1 >> i) & 1) && visxor[i]) {
s1 ^= visxor[i];
}
}
}
cout << s1 * 2 + s;
return 0;
}
题目2:CF895C
题意
给定长度为 的数组 ,问存在多少种不同的方式从数组元素中选择非空子集,使得它们的乘积为完全平方数。
思路
题意转化:乘积为完全平方数,即这个乘积分解质因数后的每个数都是偶数次幂,即将每个 转成一个长度为 的二进制数 ,第 位表示 分解质因数后第 个质数的幂次是奇数还是偶数。答案为多少种选择方式使得最后的异或和为 。
考虑答案,把数插入线性基后。不在线性基上的 ,无所谓选不选;在线性基上的 选法是固定的。答案为 , 为不在线性基上的点,注意减去空集。
时间复杂度:
#include <iostream>
using namespace std;
using ll = long long;
const int MAXV = 19, Mod = 1e9 + 7;
const int prime[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67};
int n, c, ans = 1, visxor[MAXV];
void Insert(ll x) {
for (int i = 18; i >= 0; i--) {
if ((x >> i) & 1) {
if (!visxor[i]) {
visxor[i] = x, c--; // 不在线性基上的点减少一个
return ;
}
x ^= visxor[i];
}
}
return ;
}
int get_prime(int x) { // 求 b[i]
int ret = 0;
for (int i = 0; i < 19; i++) {
int u = 0;
for (; x % prime[i] == 0; u ^= 1, x /= prime[i]);
ret += (u << i);
}
return ret;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n, c = n;
for (int i = 1, x; i <= n; i++) {
cin >> x, Insert(get_prime(x));
}
for (; c--; (ans *= 2) %= Mod);
cout << (ans + Mod - 1) % Mod;
return 0;
}
本文作者:xiehanrui0817
本文链接:https://www.cnblogs.com/xhr0817-blog/p/18421593
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步