线性基#
概述#
通常可以解决有关异或的一些题目。
对于给定的数集 ,线性基是一个子集 ,其满足:
- 原序列里面的任意一个数,都可以由线性基里面的若干个数按位异或得到;
- 任意若干个数,异或和不为 .
- 是满足上述条件的最小子集。
能求出:给定集合的最大异或和。
构造#
//插入一个long long范围内的整数
int p[65];
void insert(int x) {
for(int i = 62; i >= 0; i--) {
if(x & (1ll << i)) {
if(!p[i]) { p[i] = x; break; }
x ^= p[i];
}
}
}
cpp在二进制下, 代表线性基中,最高位是第 位的元素。
显然每个数位最多需要一个元素,因为当 时,所有数都能被线性基表出。
上面这段代码中,用 表示 x
在运行过程中的取值,那么其含义为:要让 能被线性基表出,则只需 可以被线性基表出。
循环第一次进行时,我们需要 能被表出,用 表示二进制下 的最高位,则:
- 若 为空,,直接填入后跳出循环。
- 若 非空,由于 已经可以被表出了,要想让 能被表出,就只需要 可以被表出。所以让 ,注意到 的最高位会变小,会在后面的循环中被处理,这很合理。
若当循环结束时, 还没有被执行过,说明 在插入前就可以被线性基表出了。
查询异或最值#
还没写完
前缀线性基#
考虑线性基的插入过程,线性基上每一位的数,都是对应位上在原数列最左侧的数字。现在我们进行改变,使得线性基上每一位的数,都变成对应位上在原数列最右侧的数字。
代码实现:
struct Linear_Basis{
int p[24], pos[24];
void insert(int x, int id) {
for(int i = 23; i >= 0; i--) {
if(x & (1ll << i)) {
if(!p[i]) { p[i] = x, pos[i] = id; break; }
else if(id > pos[i]) { swap(pos[i], id), swap(x, p[i]); }
x ^= p[i];
}
}
}
int qry_max(int l) {
int ret = 0;
for(int i = 23; i >= 0; i--)
if(pos[i] >= l && (ret ^ p[i]) > ret) ret ^= p[i];
return ret;
}
} lb;
cpp尽可能用右侧的数来作为基,所以要用 替换掉 的位置。
插入之后需要 ,因为此时的线性基已经能表出 了,但原先的 不能被表示了。
直接取出所有 的 ,就是 的线性基。
原因:还是要看上面插入函数流程的含义。若一个 所对应的 ,说明在插入 这些数时, 是否存在于 能否被表出是无关的。因此 的 不在 的线性基中。