线性基的学习+总结
参考博客:线性基详解
\(Summary:\)
- 原序列里面的任意一个数都可以由线性基里面的一些数异或得到
- 线性基里面的任意一些数异或起来都不能得到 0
- 线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
- 一个序列的线性基不唯一,只是元素数量唯一而已。
\(add\) 函数 ,将一个数 \(x\) 插入线性基
void add(long long x) {
tot = 1;//tot 表示线性基元素的数量
for (int i = 60; i >= 0; i--) {
if(d[i]) tot++;
if (x & (1ll << i)){
if (d[i]) x ^= d[i];
else {
d[i] = x;
break;//插入成功就退出
}
}
}
}
首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为 \(0\)。
long long MaxVal() {
long long ans = 0;
for (int i = 60; i >= 0; i--)//记得从线性基的最高位开始
if ((ans ^ d[i]) > ans)ans ^= d[i];
return ans;
}
显然就是最小的 \(d[i]\) 了,因为最小的 \(d[i]\)无论异或谁都会变大。
如果是求整个序列能异或出的最小值
而不是这个序列的线性基能异或出的最小值的
话,要另外看一看有没有元素不能插入线性基,如果有,那么最小值就是 \(0\),否则依然是最小的 \(d[i]\)。
void work()//处理线性基,使得对于 d[x]一定是第x位为1,可以异或成的最小值
{
for (int i = 1; i <= 60; i++)
for (int j = 1; j <= i; j++)
if (d[i] & (1ll << (j - 1)))d[i] ^= d[j - 1];
//注意这个i也是从小到大枚举的,所以异或完之后,对于d[i]转化成二进制之后,如果低x位为1,那么说明d[x]=0
}
long long k_th(long long k) {
if (k == 1 && tot < n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
if (tot < n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
work();
long long ans = 0;
for (int i = 0; i <= 60; i++) {
if (d[i] != 0) {
if (k % 2 == 1)ans ^= d[i];
k /= 2;
/*
* 这里解释一下为什么是这样的
* 首先明确此时的线性基已经是最小的了
* 假设k的二进制表示是 1101,那么说明这个组成是第一个线性基存在,第二个不存在,第三个和第四个不存在
* 这样表示的一定是第k个,前面就k-1个有:0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100
*/
}
}
}
原文:https://www.cnblogs.com/EchoZQN/p/13398482.html