线性基
定义
线性基用于解决类似于: "给定 \(n\) 个数, 取任意个数, 使得异或值最大"此类问题
本质上还是求出多个数最大和最小的异或和,由于给出的数目较大, 如果直接一一比较的话时间复杂度较大,可以通过线性基优化, 假如有 \(n\) 个, 其中最大的数二进制位数为 \(m\) 位, 那么线性基可以把原来的 \(2^n\) 的组合优化到 \(2^m\), 一次的时间复杂度为 \(m\). 虽然理论上有 \(2^n\) 种组合, 但是由于: 任意两个数 \(a,b\) 做异或运算之后不会比他们两者的最高二进制位还要高, 所以 \(2^n\) 种组合中最大的数也就是\(2^m - 1\) 也就是异或结果会在: \([0\) ~ \(2^m - 1]\) 这个区间内
这里介绍一下什么是线性基, 举个形象的例子来说, \(A = [2, 3, 5, 6, 7]\) 那么它的任意组合有 \(2^n - 1\) 种, 对 \(A\) 中所有的数做异或运算之后得到所有的可能性: \([0, 1, 2, 3, 4, 5, 6, 7] = 8\) 种, \(A\) 中最大的数字二进制位是 \(3\) 位, 此时我们有个线性基 \(P = [5, 2 , 1]\), 有 \(2^3 - 1 = 7\) 种, 注意线性基不能处理异或为 \(0\) 的可能性, 故此时为 \(7\) 种, 注意, 线性基不是唯一的
算法
接下来考虑如何构造线性基, 下面通过分析两个例子
\((1).\) 首先分析一种特例, \(A\) 中所有的元素的二进制位均不同, 那么 \(A\) 自身就是一个线性基, 例如 \(A = [1, 3, 9] = [1, 11, 1001]\), 那么很明显是 \(1, 2, 4\) 位, 均不同, 此时 \(A\) 自己就是自己的线性基, 因为如果我需要异或出一个长度为 \(4\) 的数, 那么 \(P\) 中必须有长度为 \(4\) 的数, 对于其他数字同理
\((2).\) 对于 \(A\) 中的数字较为繁杂, 含有相同位数的数字, 我们这样处理, 对于任意相同的数字, 直接异或即可. 先讨论两个位数相同的线性基构造. 设 \(A = [a_1, a_2]\) 且两者二进制位数相同, 那么其线性基为: \(P = [a_1, a_1\oplus{a_2}]\), 下面给出证明: 对于 \(A = [a_1, a_2]\) 的组合: \([a_1, a_2, a_1 \oplus{a_2}]\) , 而 \(P\) 的组合为: \([a_1, a_1 \oplus {a_2}, a_1 \oplus{a_1} \oplus{a_2} = 0 \oplus{a_2} = a_2]\), 由此可见, 两者相同, 那么我们对于相同位的直接做异或处理即可.
- 最小异或和
由于最小异或和就一个值, 那么不是 \(0\) 就是位数最小的那个数字 - 最大异或和
考虑贪心, 对于每一位的数字, 我们直接做异或看看是否增大, 若增大, 我们就直接加进去, 为什么这样是对的? 因为我们从大到小开始, 对于最大的, 肯定不会存在一个其它的数可以到达最高位. 对于次大的, 若异或后对答案有益, 我们就异或, 因为不会存在一个更大的数可以使答案更优, 换句话说, 比它小的元素任意的组合都不会比它大.
例题
下面给出例题: 线性基
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 7;
signed main()
{
std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n; cin >> n;
vector<int> p(65);
bool zero;
auto insert = [&](int x) -> void{
for(int i = 61; i >= 1; i--){
if(x >> (i - 1)){
if(p[i] == 0){
p[i] = x;
return;
}
else x ^= p[i];
}
}
zero = true;
};
for(int i = 1; i <= n; i++){
int x; cin >> x;
insert(x);
}
int res = 0;
for(int i = 61; i >= 1; i--){
res = max(res, res ^ p[i]);
}
cout << res << '\n';
return 0;
}

浙公网安备 33010602011771号