线性基
线性基是一种擅长处理异或问题的数据结构。
可以用来:
- 查询一个数是否可以被一堆数异或出来。
- 查询一堆数可以异或出来的最值.
- 查询一堆数可以异或出来的第
大值。
以上三点均可以在
线性基本质上是一个通过对二进制的判断来从原集合中取出一些数成为个新的集合。
它有一些性质:
-
线性基具有普通集合所具有的性质,即确定性、互异性、无序性。
-
线性基中每个数二进制下的
的最高位都是不同的,也就是说线性基中每个元素的二进制位数不同。 -
线性基中没有异或和为
的子集。 -
线性基中任意多元素的异或和的值域等于原集合中任意多元素的异或和的值域。
-
线性基在满足上一个条件的情况下,所包含元素个数是最少的。
-
线性基中不同的元素异或出来的值是不同的。
考虑构造一个线性基。假设我们要插入一个数
若第
那为什么当前位有值时需要异或当前位置上的数呢?
因为任何一个数二进制的最高位都是
P3812 【模板】线性基
模版题,要求异或和最大。
我们从高位往低位扫,如果当前答案更大就更新答案。
为什么这样可以求出最大异或和?
因为线性基中每个数最高位不同,也就是说每一个位是最高位的情况最多只有一次。假设当前扫到了第
代码中还给出了此题以外的几种线性基操作。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int kmax = 53;
int n;
long long p[kmax];
bool zero; // 记录有无值为0的元素
void Insert(long long x) { // 插入元素
for (int i = kmax - 1; ~i; i--) {
if (x & (1ll << i)) {
if (!p[i]) { // 未出现过
p[i] = x;
return;
}
x ^= p[i]; // 否则异或
}
}
}
bool In(long long x) { // 判断一个数是否能通过线性基表示
for (int i = kmax - 1; ~i; i--) {
if (x & (1ll << i)) {
x ^= p[i];
}
}
return !x;
}
long long Max() { // 最大值
long long mx = 0;
for (int i = kmax - 1; ~i; i--) {
mx = max(mx, mx ^ p[i]);
}
return mx;
}
long long Min() { // 最小值
if (zero) return 0;
for (int i = 0; i < kmax; i++) {
if (!p[i]) continue;
return p[i];
}
return -1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
long long x;
scanf("%lld", &x);
Insert(x);
}
printf("%lld\n", Max());
return 0;
}
回顾求解线性基的过程,会发现类似于高斯消元。我们尝试借助高斯消元法理解线性基的过程。
设原数组
这里以求解
正确性证明:
中元素的组合可以由最初矩阵中行的组合表示。- 最初矩阵和最终矩阵可以相互转化。
- 最终矩阵每行位数不同,满足线性基的性质。
此外,当最终矩阵中有不少于两行全为
代码:
void Gauss() { // 高斯消元求线性基
int num = 1;
for (long long i, j = 1ll << 62; j; j >>= 1) {
for (i = num; i <= n; i++) {
if (p[i] & j) break;
}
if (i <= n) {
swap(p[i], p[num]);
for (i = 1; i <= n; i++) {
if (i == num) continue;
if (p[i] & j) {
p[i] ^= p[num];
}
}
num++;
}
}
zero = --num != n;
n = num;
}
接下来是一些练习题。
P3857 [TJOI2008] 彩灯
就是问原数组能异或出多少种不同的状态。
对原数组求解线性基,由线性基的性质,线性基中每种组合异或出来的状态都是不同的。
设线性基中有
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int kmax = 53;
const int Mod = 2008;
int n, m;
long long res;
long long num, p[kmax];
char ch[kmax];
void Insert(long long x) {
for (int i = kmax - 1; ~i; i--) {
if (x >> i & 1) {
if (!p[i]) {
p[i] = x; // 插入线性基
res++; // 个数累计
return;
}
x ^= p[i];
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%s", ch);
num = 0;
for (int i = 0; i < strlen(ch); i++) {
num += (1ll << (n - i)) * (ch[i] == 'O'); // 将字符转化
}
Insert(num); // 插入
}
res = (1ll << res) % Mod;
printf("%lld\n", res);
return 0;
}
P4301 [CQOI2013] 新Nim游戏
先手要胜利,必须让后手无论拿掉哪几堆都无法使异或和等于
这时要用到线性基。
如果一个数没能插入线性基,说明该数能与线性基中一些数异或成
先手必然胜利,因此只需考虑如何使第一回合先手拿的火柴数最少。
容易想到将原数组中的元素从大到小插入,这样不能插入的数都尽可能的小,能使总和最小。
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int kmax = 105;
const int kmaxM = 31;
int n, a[kmax];
long long p[kmax];
long long res;
bool Insert(int x) {
for (int i = kmaxM - 1; ~i; i--) {
if (x & (1ll << i)) {
if (!p[i]) {
p[i] = x;
return 1; // 插入成功
}
x ^= p[i];
}
}
return 0; // 插入失败
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + n + 1, [](int x, int y) { return x > y; }); // 从大到小插入
for (int i = 1; i <= n; i++) {
if (!Insert(a[i])) { // 若不能插入
res += a[i]; // 就要拿走
}
}
printf("%lld\n", res);
return 0;
}
P4570 [BJWC2011] 元素
贪心,将每种矿石根据魔力从大到小插入线性基。
考虑这样插入的正确性。
假设一个子集
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int kmax = 1003;
const int kmaxM = 64;
struct V {
long long x, w;
} v[kmax];
int n;
long long p[kmaxM], res;
bool Check(long long x) {
for (int i = kmaxM - 1; ~i; i--) {
if (x & (1ll << i)) {
if (!p[i]) {
p[i] = x;
return 1;
}
x ^= p[i];
}
}
return 0;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i].x >> v[i].w;
}
sort(v + 1, v + n + 1, [](V p, V q) { return p.w > q.w; });
for (int i = 1; i <= n; i++) {
if (Check(v[i].x)) {
res += v[i].w;
}
}
cout << res;
return 0;
}
P4151 [WC2011] 最大XOR和路径
将一条路径拆成环和链两部分。选择一条链,考虑用环对其进行增广。
假设一条路径
所以任选一条链为起始值,枚举所有环并将异或和插入线性基。最后求最大异或和即可。
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int kmax = 1e5 + 3;
const int kmaxM = 64;
struct E {
int p, y;
long long w;
} e[kmax << 1];
int n, m;
int h[kmax], ec;
long long p[kmaxM], res[kmax], ress;
bool b[kmax];
void Addedge(int x, int y, long long w) {
e[++ec] = {h[x], y, w};
h[x] = ec;
}
void Insert(long long x) {
for (int i = kmaxM - 1; ~i; i--) {
if (x & (1ll << i)) {
if (!p[i]) {
p[i] = x;
return;
}
x ^= p[i];
}
}
}
void Dfs(int x, int fa, long long c) {
b[x] = 1;
res[x] = c;
for (int i = h[x]; i; i = e[i].p) {
int y = e[i].y;
if (y == fa) continue;
if (!b[y]) {
Dfs(y, x, c ^ e[i].w);
} else {
Insert(c ^ e[i].w ^ res[y]); // 插入线性基
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1, x, y; i <= m; i++) {
long long w;
scanf("%d%d%lld", &x, &y, &w);
Addedge(x, y, w);
Addedge(y, x, w);
}
Dfs(1, 0, 0);
ress = res[n];
for (int i = kmaxM - 1; ~i; i--) {
ress = max(ress, ress ^ p[i]); // 求最大值
}
printf("%lld\n", ress);
return 0;
}
完结撒花 \ / \ / \ /
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效