【算法笔记】线性基
线性基
定义:给定数集\(s\),以异或运算张成的数集与\(S\)相同的极大线性无关集,称为原数集的一个线性基。
性质 :
- 原数集的任意一个数都能有线性基内部的一些数异或得到。
- 线性基内部任意数异或不为 0
- 线性基内数唯一,且保证性质一的情况下,数的个数最少。
- 线性基内每个数的最高有效位各不相同。
如何判断原数集能否异或出\(0\),在插入的过程中如果插入失败就可以异或出\(0\)。
struct Basis {
vector<ui64> B;
bool zero, sorted;
Basis() {
B = vector<ui64>();
zero = false, sorted = true;
}
void sort() {
if (sorted) return;
sorted = true;
ranges::sort(B);
return;
}
void insert(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
if (x == 0) {
zero = true;
return;
}
for (auto &b: B)
b = min(b, b ^ x);
B.push_back(x), sorted = false;
return;
}
int query(int kth = 1) { // query k-th smallest element
sort();
if (zero) kth--;
ui64 ans = 0;
for (auto b: B) {
if (kth & 1) ans ^= b;
kth >>= 1;
}
if (kth == 0) return ans;
return -1;
}
ui64 max() {
ui64 ans = 0;
for (auto b: B)
ans ^= b;
return ans;
}
bool check(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
return x == 0;
}
void merge(const Basis &oth) {
for (auto x: oth.B)
insert(x);
return;
}
};
例题
P3812 【模板】线性基
验证模板,考虑到线性基内数的最高有效位各不相同,所以直接把所有的数异或就是答案
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ui64 = uint64_t;
struct Basis {
vector<ui64> B;
bool zero, sorted;
Basis() {
B = vector<ui64>();
zero = false, sorted = true;
}
void sort() {
if (sorted) return;
sorted = true;
ranges::sort(B);
return;
}
void insert(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
if (x == 0) {
zero = true;
return;
}
for (auto &b: B)
b = min(b, b ^ x);
B.push_back(x), sorted = false;
return;
}
int query(int kth = 1) { // query k-th smallest element
sort();
if (zero) kth--;
ui64 ans = 0;
for (auto b: B) {
if (kth & 1) ans ^= b;
kth >>= 1;
}
if (kth == 0) return ans;
return -1;
}
ui64 max() {
ui64 ans = 0;
for (auto b: B)
ans ^= b;
return ans;
}
bool check(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
return x == 0;
}
void merge(const Basis &oth) {
for (auto x: oth.B)
insert(x);
return;
}
};
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
Basis b;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
ui64 x;
cin >> x;
b.insert(x);
}
cout << b.max();
return 0;
}
P3857 [TJOI2008] 彩灯
线性基的元素有\(x\)个,每一个都有选或不选两种,方案数\(2^x\)
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ui64 = uint64_t;
struct Basis {
vector<ui64> B;
bool zero, sorted;
Basis() {
B = vector<ui64>();
zero = false, sorted = true;
}
void sort() {
if (sorted) return;
sorted = true;
ranges::sort(B);
return;
}
void insert(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
if (x == 0) {
zero = true;
return;
}
for (auto &b: B)
b = min(b, b ^ x);
B.push_back(x), sorted = false;
return;
}
int query(int kth = 1) { // query k-th smallest element
sort();
if (zero) kth--;
ui64 ans = 0;
for (auto b: B) {
if (kth & 1) ans ^= b;
kth >>= 1;
}
if (kth == 0) return ans;
return -1;
}
ui64 max() {
ui64 ans = 0;
for (auto b: B)
ans ^= b;
return ans;
}
bool check(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
return x == 0;
}
void merge(const Basis &oth) {
for (auto x: oth.B)
insert(x);
return;
}
};
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
Basis b;
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
string s;
cin >> s;
i64 val = 0;
for (auto c: s) {
val <<= 1;
val |= (c == 'O');
}
b.insert(val);
}
int t = b.B.size(), res = 1;
for (int i = 0; i < t; i++)
res = res * 2 % 2008;
cout << res;
return 0;
}
P4570 [BJWC2011] 元素
首先选出的集合内不能存在异或和为\(0\)的情况,这个可以用线性基来维护。
值最大可以贪心,优先插入魔力值大的。假设集合内存在\(a\oplus b \oplus c \oplus d = 0\)的情况,我们肯定扔掉最小值最优。
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ui64 = uint64_t;
struct Basis {
vector<ui64> B;
bool zero, sorted;
Basis() {
B = vector<ui64>();
zero = false, sorted = true;
}
void sort() {
if (sorted) return;
sorted = true;
ranges::sort(B);
return;
}
void insert(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
if (x == 0) {
zero = true;
return;
}
for (auto &b: B)
b = min(b, b ^ x);
B.push_back(x), sorted = false;
return;
}
int query(int kth = 1) { // query k-th smallest element
sort();
if (zero) kth--;
ui64 ans = 0;
for (auto b: B) {
if (kth & 1) ans ^= b;
kth >>= 1;
}
if (kth == 0) return ans;
return -1;
}
ui64 max() {
ui64 ans = 0;
for (auto b: B)
ans ^= b;
return ans;
}
bool check(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
return x == 0;
}
void merge(const Basis &oth) {
for (auto x: oth.B)
insert(x);
return;
}
};
using vi = vector<i64>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vi number(n), magic(n), p(n);
for (int i = 0; i < n; i++)
cin >> number[i] >> magic[i], p[i] = i;
ranges::sort(p, [&](const int x, const int y) {
return magic[x] > magic[y];
});
Basis b;
i64 res = 0;
for (auto i: p) {
if (b.check(number[i])) continue;
b.insert(number[i]), res += magic[i];
}
cout << res;
return 0;
}
P4301 [CQOI2013] 新Nim游戏
首先根据Nim游戏的性质,我们知道如果初始的异或和不为\(0\),先手必胜。
假设在第一轮先手操作后异或和不为 0,后手是不可能通过拿操作是的异或为 0 的。对于先手来说,极端情况就是保留一个,此时异或和一定不为0。所以先手必胜。
先手选取的方式用线性基来维护,总数最小参考上一题的贪心处理就好了。
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ui64 = uint64_t;
struct Basis {
vector<ui64> B;
bool zero, sorted;
Basis() {
B = vector<ui64>();
zero = false, sorted = true;
}
void sort() {
if (sorted) return;
sorted = true;
ranges::sort(B);
return;
}
void insert(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
if (x == 0) {
zero = true;
return;
}
for (auto &b: B)
b = min(b, b ^ x);
B.push_back(x), sorted = false;
return;
}
int query(int kth = 1) { // query k-th smallest element
sort();
if (zero) kth--;
ui64 ans = 0;
for (auto b: B) {
if (kth & 1) ans ^= b;
kth >>= 1;
}
if (kth == 0) return ans;
return -1;
}
ui64 max() {
ui64 ans = 0;
for (auto b: B)
ans ^= b;
return ans;
}
bool check(ui64 x) {
for (auto b: B)
x = min(x, b ^ x);
return x == 0;
}
void merge(const Basis &oth) {
for (auto x: oth.B)
insert(x);
return;
}
};
using vi = vector<i64>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vi a(n);
for (auto &i: a) cin >> i;
ranges::sort(a, greater<>());
Basis b;
i64 res = 0;
for (auto &i: a) {
if (b.check(i)) res += i;
else b.insert(i);
}
cout << res;
return 0;
}