基:在线性代数中,基(也称为基底)是描述、刻画向量空间的基本工具。向量空间的基是它的一个特殊的子集,基的元素称为基向量。向量空间中任意一个元素,都可以唯一地表示成基向量的线性组合。如果基中元素个数有限,就称向量空间为有限维向量空间,将元素的个数称作向量空间的维数。
同样的,线性基是一种特殊的基,它通常会在异或运算中出现,它的意义是:通过原集合S的某一个最小子集S1使得S1内元素相互异或得到的值域与原集合S相互异或得到的值域相同。
——百度百科
例题一:【JLOI 2015】装备购买
线性基模版题。按装备的价格排序,然后一边高斯消元,一边贪心的选装备即可。具体高斯消元过程见代码。
// This Template is Very Important
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long double ldb;
const int maxn = 505;
const ldb eps = 1e-8;
int n, m, base[maxn];
struct weapon {
ldb a[maxn];
int v;
bool operator<(const weapon &o) const{
return v < o.v;
}
} w[maxn];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int k, j = 1; j <= m; j++) {
scanf("%d", &k);
w[i].a[j] = k;
}
}
for (int i = 1; i <= n; i++) {
scanf("%d", &w[i].v);
}
sort(w + 1, w + n + 1);
int res = 0, ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (fabs(w[i].a[j]) > eps) {
if (!base[j]) {
base[j] = i;
res++, ans += w[i].v;
break;
} else {
ldb d = w[i].a[j] / w[base[j]].a[j];
for (int k = j; k <= m; k++) {
w[i].a[k] -= d * w[base[j]].a[k];
}
}
}
}
}
printf("%d %d\n", res, ans);
return 0;
}
例题二:【HDU 3949】XOR
异或空间线性基。具体求法见代码。
思路:先求出线性基,从基底中选出
m
m
个数共有种选法,与
2m
2
m
种异或值一一对应。
于是,我们将询问的
k
k
二进制分解,就对应了基的一种选法。不难用二进制思想证明。
细节:注意是否可以得到的情况。如果能得到 0 0 ,就将二进制分解。
// This Template is Also Very Important
#include <cstdio>
#include <iostream>
using namespace std;
typedef unsigned long long ull;
const int maxn = 10005;
ull a[maxn];
int tasks, n, m, zero, t;
int main() {
scanf("%d", &tasks);
for (int task = 1; task <= tasks; task++) {
printf("Case #%d:\n", task);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%llu", a + i);
}
zero = 0, t = n;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (a[j] > a[i]) {
swap(a[i], a[j]);
}
}
if (!a[i]) {
zero = 1, t = i - 1;
break;
}
for (int k = 63; ~k; k--) {
if (a[i] >> k & 1) {
for (int j = 1; j <= n; j++) {
if (i != j && a[j] >> k & 1) {
a[j] ^= a[i];
}
}
break;
}
}
}
for (scanf("%d", &m); m--; ) {
ull k, ans = 0;
scanf("%llu", &k);
k -= zero;
if (k >= 1ull << t) {
puts("-1");
continue;
}
for (int i = t - 1; ~i; i--) {
if (k >> i & 1) {
ans ^= a[t - i];
}
}
printf("%llu\n", ans);
}
}
return 0;
}
用类似例题二的做法可以求出每个数的排名,但是我们还不知道每个异或值在数列中出现了几次。
定理:每个异或值出现了 2n−m 2 n − m 次,其中 m m 为线性基的大小。
这样,我们就可以用快速幂算出答案了。
#include <cstdio>
#include <iostream>
using namespace std;
const int mod = 10086;
const int maxn = 100005;
int n, m, q, a[maxn], b[maxn];
int mpow(int x, int y) {
int z = 1;
for (; y; y >>= 1) {
if (y & 1) {
z = z * x % mod;
}
x = x * x % mod;
}
return z;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
scanf("%d", &q);
m = n;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (a[i] < a[j]) {
swap(a[i], a[j]);
}
}
if (a[i] == 0) {
m = i - 1;
break;
}
for (int k = 29; k >= 0; k--) {
if (a[i] >> k & 1) {
b[i] = k;
for (int j = 1; j <= n; j++) {
if (i != j && a[j] >> k & 1) {
a[j] ^= a[i];
}
}
break;
}
}
}
int ans = 0;
for (int i = m - 1; i >= 0; i--) {
// printf("%d %d\n", b[m - i], 1 << i);
if (q >> b[m - i] & 1) {
ans += 1 << i;
}
}
ans = (ans % mod * mpow(2, n - m) + 1) % mod;
printf("%d\n", ans);
return 0;
}
例题四:【WC 2011】Xor
这题将集合中的问题搬到了图上。我们发现一个性质:到 n n 的一条路径的异或值必定等于到 n n 的一条简单路径的异或值异或图中一些简单环的边权异或值。
接下来就好办了。找到图中的每一个环的异或值,再随便找一条到 n n 的简单路径,问题就变成了找到一个环的子集,使得它们的异或值异或到 n n 的那条简单路径的值最大。显然可以通过贪心来解决。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 50005;
const int maxm = 100005;
bool vis[maxn];
int n, m, t, cnt;
int tot, nxt[maxm], ter[maxm], lnk[maxn];
ll a[maxn + maxm * 2], sum[maxn], val[maxm];
void addedge(int u, int v, ll w) {
ter[tot] = v;
val[tot] = w;
nxt[tot] = lnk[u];
lnk[u] = tot++;
}
void dfs(int u, int p) {
vis[u] = 1;
ll w;
for (int v, i = lnk[u]; ~i; i = nxt[i]) {
v = ter[i], w = val[i];
if (v == p) {
continue;
}
if (vis[v]) {
a[++cnt] = w ^ sum[u] ^ sum[v];
} else {
sum[v] = sum[u] ^ w;
dfs(v, u);
}
}
}
int main() {
memset(lnk, -1, sizeof(lnk));
scanf("%d %d", &n, &m);
ll w;
for (int u, v, i = 1; i <= m; i++) {
scanf("%d %d %lld", &u, &v, &w);
addedge(u, v, w);
}
dfs(1, 0);
t = cnt;
for (int i = 1; i <= cnt; i++) {
for (int j = i + 1; j <= cnt; j++) {
if (a[i] < a[j]) {
swap(a[i], a[j]);
}
}
if (a[i] == 0) {
t = i - 1;
break;
}
for (int k = 59; k >= 0; k--) {
if (a[i] >> k & 1) {
for (int j = 1; j <= cnt; j++) {
if (i != j && a[j] >> k & 1) {
a[j] ^= a[i];
}
}
break;
}
}
}
ll ans = sum[n];
for (int i = 1; i <= t; i++) {
if ((ans ^ a[i]) > ans) {
ans ^= a[i];
}
}
printf("%lld\n", ans);
return 0;
}