前两天作了一套题,有一道线性基的模版题没出,觉得有必要学一手线性基了…
一.线性基是什么?
线性基其实就是一个集合。假设一个普通的集合为A,它的线性基集合B是A的一个子集。B中所有子集的异或和组成的集合等价于A中所有子集的亦或和组成的集合(除0外)。
二.线性基有什么性质:
1.若一个数x能被线性基集合中某个子集的异或和得到,该子集唯一
2.线性基集合中任意一个子集的异或和不为0
3.线性基集合中每个元素的最高位互不相同
4.若线性基集合是满的(也就是说最高位是1~n位的数都存在),那么它的异或集合是【1, 2^n - 1】
三.关于线性基的代码实现:
我们用p数组存储线性基的元素
(1)关于线性基中元素的插入
我们要插入一个数x,需要从高到低枚举所有位,若x&当前位 != 0,若当前位不存在线性基元素,则将x当作线性基的当前位元素,否则x异或当前位元素,直到x为0或将x添加到线性基中
代码如下 :
void addx (ll x) {
for (ll i = 62; i >= 0; i--) { //long long型的最大值为2^63 - 1,因此是0~62
if (x >> i & 1) {
if (!p[i]) {
p[i] = x;
break;
}
x ^= p[i];
}
}
return;
}
(2)关于求最大异或和:
初始化res = 0,从高到低遍历位数,若res ^ p[i]大于res就更新res,最后res即为线性基所能得到的最大异或和
代码如下:
ll getmax() {
ll res = 0;
for (ll i = 62; i >= 0; i--) {
if (res < (res ^ p[i])) res ^= p[i];
}
return res;
}
(3)关于求异或t后的最大异或和:
同上一样,只需要初始化res = t即可。
代码如下:
ll getmaxt() {
ll res = t;
for (ll i = 62; i >= 0; i--) {
if (res < (res ^ p[i])) res ^= p[i];
}
return res;
}
(4)关于求最小异或和:
最小值就是线性基中位数由低到高第一个存在的p[i]
代码如下:
ll getmin () {
for (ll i = 0; i <= 62; i++) {
if (p[i]) return p[i];
}
return 0;
}
(5)关于求异或集合中的第k小值
需要把线性基集合中的元素改为除了最高位为1,其余位为0的形式。也就是说,若j<i且p[i]中第j位是1,就p[i] ^ p[j]
我们对k进行二进制拆分,若第i位为1,就异或p[i],最终得到的答案就是第k小的异或值
代码如下:
int tot = -1;
void build () {
for (ll i = 62; i >= 0; i--) {
for (ll j = i - 1; j >= 0; j--) {
if (p[i] >> j & 1) p[i] ^= p[j];
}
}
for (ll i = 0; i <= 62; i++) {
if (p[i]) g[++tot] = p[i];
}
}
ll query (ll k) {
//若原数组中存在元素0,则此时需要k--
if (k >= (1LL * tot)) return -1;
ll res = 0;
for (ll i = tot; i >= 0; i--) {
if (k >> i & 1) res ^= g[i];
}
return res;
}
四. 线性基能求解什么问题?
(1). 给出一个数组,问一个数能否由数组中的元素异或得到
(2).给出一个数组,问数组中全部子集异或和组成的集合中,第k小的数是多少
(3).给出一个数组和一个数t,问数组全部子集的异或和(可重复)按从小到大排序,第一个等于t的下角标是多少?
(4).给出一个可重集合和k,等概率选一个子集A,得到A的异或和x,问x^k的数学期望值是多少?
(5).给出一个连通无向图(n个点m条边,每个边有一个权值),问从起点走到终点异或和最小的值是多少?
以上就是总结的线性基的基础,遇到异或问题,可以首先考虑能否使用线性基,就酱~
如果有写的不对或者不全面的地方 可通过主页的联系方式进行指正,谢谢