来源
https://blog.sengxian.com/algorithms/linear-basis
强烈推荐这篇文章,以下内容大部分来自于此。
前言
这篇是根据以上的博客再结合自己的浅薄理解写的,讲得并不详细,因为弱鸡本人还没完全懂,先学会使用吧。
概述
首先所谓的线性基,就是把若干个整数,看成 n 维 01 向量(通常 n 不会超过 64,即 long long 的范围),然后求这些向量的一个极大无关组。需要注意的一点是,对于这里的 01 向量,只有异或运算。
下面是利用高斯消元求解线性基的代码
void cal() {//a[i]为原来的数,b[i]为新的向量 for (int i = 0; i < n; ++i) for (int j = MAX_BASE; j >= 0; --j) if (a[i] >> j & 1) { if (b[j]) a[i] ^= b[j]; else { b[j] = a[i]; for (int k = j - 1; k >= 0; --k) if (b[k] && (b[j] >> k & 1)) b[j] ^= b[k]; for (int k = j + 1; k <= MAX_BASE; ++k) if (b[k] >> j & 1) b[k] ^= b[j]; break; } }
对于最后得到的矩阵,如果第 i 的主对角线上为 1,此时我们称第 i 位存在于线性基中。
例题
SGU - 275
给定 n(1≤n≤100000) 个数a1,a2,…,an,请问这些数能够组成的最大异或和是什么?
分析
我们求出向量空间 V 的一组线性基。则答案就是将线性基中所有向量异或起来得到的向量所对应的数
详细证明看最上面的链接。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAX_BASE = 63; ll b[200]; ll a[11234]; void cal(int n) { for (int i = 0; i < n; i++) { for (int j = MAX_BASE; j >= 0; j--) { if (a[i] >> j & 1) { if (b[j]) a[i] ^= b[j]; else { b[j] = a[i]; for (int k = j - 1; k >= 0; k--) if (b[k] && b[j] >> k & 1) b[j] ^= b[k]; for (int k = j + 1; k <= MAX_BASE; k++) if (b[k] >> j & 1) b[k] ^= b[j]; break; } } } } } int main() { int n; while (~scanf("%d", &n)) { memset(b, 0, sizeof(b)); for (int i = 0; i < n; i++) { scanf("%lld", a + i); } cal(n); ll ans = 0; for (int i = 0; i <= MAX_BASE; i++) { if (b[i] >> i & 1) ans ^= b[i]; } printf("%lld ", ans); } }
HDU - 3949
给定 n(n≤10000) 个数 a1,a2,…,an,以及 Q(Q≤10000) 个询问,每次询问这些数(至少一个,不能不选)能够组成的异或和中第 k 小的数是什么(去掉重复的异或和)。
分析
我们求出向量空间 V 的一组线性基 B。因为向量空间中的任意向量u 均可以被表示称B 中向量的唯一线性组合,所以B 中的任意非空子集都可以构成一个向量且互不重复。所以这些数能够组成的异或和的个数为 2^∣B∣−1,特别的,如果 ∣B∣<n,则必然存在一个向量组满足线性相关性,这个向量组也就能通过线性组合,异或得到 0,则异或和的个数为 2^∣B∣。
假设线性基中有 m 个基向量,从小到大依次为 (v0,…,vm−1),则第 k=(bx…b0)2 小的数就是: ⊕(0≤i≤x)bi⋅vi
// Created by Sengxian on 2016/12/5. // Copyright (c) 2016年 Sengxian. All rights reserved. // HDOJ 3949 线性基 #include <bits/stdc++.h> using namespace std; typedef long long ll; inline ll readLL() { static ll n; static int ch; n = 0, ch = getchar(); while (!isdigit(ch)) ch = getchar(); while (isdigit(ch)) n = n * 10 + ch - '0', ch = getchar(); return n; } const int MAX_N = 100000 + 3, MAX_BASE = 60; int n, zero = false; ll a[MAX_N], b[MAX_BASE + 3]; vector<ll> mmap; void prepare() { int cnt = 0; memset(b, 0, sizeof b); for (int i = 0; i < n; ++i) for (int j = MAX_BASE; j >= 0; --j) if (a[i] >> j & 1) { if (b[j]) a[i] ^= b[j]; else { b[j] = a[i], cnt++; for (int k = j - 1; k >= 0; --k) if (b[k] && ((b[j] >> k) & 1)) b[j] ^= b[k]; for (int k = j + 1; k <= MAX_BASE; ++k) if ((b[k] >> j) & 1) b[k] ^= b[j]; break; } } zero = cnt != n; mmap.clear(); for (int i = 0; i <= MAX_BASE; ++i) if (b[i]) mmap.push_back(b[i]); } ll query(ll k) { if (zero) k--; if (k >= (1LL << (int)mmap.size())) return -1; ll ans = 0; for (int i = 0; i < (int)mmap.size(); ++i) if ((k >> i) & 1) ans ^= mmap[i]; return ans; } int main() { #ifdef DEBUG freopen("test.in", "r", stdin); #endif int caseNum = readLL(); for (int t = 1; t <= caseNum; ++t) { n = readLL(); for (int i = 0; i < n; ++i) a[i] = readLL(); prepare(); printf("Case #%d: ", t); int q = readLL(); for (int i = 0; i < q; ++i) printf("%lld ", query(readLL())); } return 0; }
小结
线性基的题型相对比较固定,看到下面的类型基本上都是线性基了:
- 最大异或和
- 第 kk 大异或和/异或和是第几大
- 求所有异或值的和