动态线性基可以维护一个集合,支持修改集合元素,并查询子集最大异或和。
重要引理
修改前后的线性空间维度数量的差值至多为 。
证明
假设原来线性空间基底集合为 和 ,其中 是我们需要修改的向量。
考虑若 可以表示为 的线性组合,那么维度减少 。
考虑若原集合中存在 可以表示为 和 的线性组合其其中包含 ,那么删除 后, 可以代替 ,对基底集合的贡献和 相同,这样的 尽管可能有多个,但产生的贡献都等于 ,故维度至多增加 。
证明完毕。更进一步的,新本质不同的基底集合只可能为 三种。
另一方面,线性代数告诉我们,增加或删除矩阵的一行(列),矩阵的秩至多增减 。这与这里得到的结论是类似的。
过程
与朴素线性基不同,我们不光要保存基底,还要把所有的向量一起放进来处理。当然我们需要维护一个数组方便我们取出每一维的基底。
对于集合的一个向量 ,需要将其替换为 。
先考虑集合中线性组合包含 的向量,我们需要将他们线性组合中的 取出。当然有可能的这样会对已经选出的基底产生影响,故在这些向量中,选择最高位最低的,设为 ,用 去异或上其余每个向量。这样对基底集合产生的改动只可能为 这一个向量(如果 本身是基底的话)。并且此时 的贡献已被去除。
再考虑插入,插入 与插入 是没有本质区别的,故我们直接将 异或上 按照一般并查集的方法插入即可。
实现
定义 为原数列, 由 异或而得,用以辅助维护线性基。
- 用
from[i][j]
表示 中是否包含 ; - 用
base[i][j]
表示 的第 个二进制位(从低到高); - 用
bpos[i]
表示第 位的基底的下标。
参考代码如下:
void modify(int p, const bitset<M> &x) {
int q = 0;
for (int i = 1; i <= n && !q; i++) {
if (from[i][p] && base[i].none()) q = i;
}
for (int i = 0; i < M && !q; i++) {
if (from[bpos[i]][p]) q = bpos[i], bpos[i] = 0;
}
for (int i = 1; i <= n; i++) {
if (from[i][p] && i != q) from[i] ^= from[q], base[i] ^= base[q];
}
base[q] ^= x;
for (int i = M - 1; ~i; i--)
if (base[q][i]) {
if (!bpos[i]) {
bpos[i] = q;
break;
} else {
from[q] ^= from[bpos[i]], base[q] ^= base[bpos[i]];
}
}
}
void query() {
res.reset();
for (int i = M - 1; ~i; i--) {
if (!res[i] && bpos[i]) res ^= base[bpos[i]];
}
}