线性基是向量空间的一组基,通常可以解决有关异或的一些题目。(来自OI-wiki)
反正是什么不重要,知道线性基可以解决异或的题目就行了
模板((insert))
int d[55];
void insert (int x) {
for (int i=63;i>=0;i--)
if (x&(1ll<<i)) {
if (d[i]) x^=d[i];
else {
d[i]=x;
return;
}
}
}
看不懂不要着急...先往下看
性质
对线性基通俗一点的定义是,一个由原数列构造出来的集合,它满足一些性质
-
原数列的任何一个数都可以由线性基里的元素相互异或得到,所以原数列相互异或得到的值都可以由线性基里的元素相互异或得到
证明:
根据上面给出的模板,可以发现在线性基里插入一个数只有两种结果——插入成功与失败。
分类讨论:
1.成功插入线性基
假设数字(x)插入到了线性基的第(i)个位置,但它可能已经异或了前面若干个数,于是就有:(x)$d[a]$(d[b])^(d[c])...=(d[i])
所以(d[a])$d[b]$(d[c])...(d[i])=(x)
2.插入失败
考虑为什么插入失败。是因为它插入的时候异或了若干个数之后变成了0
那类似于成功插入,它可以由线性基中已有的元素异或得到
-
线性基是满足性质1的最小集合
这..不会证..就姑且当它是对的√
-
线性基里的任何几个元素异或都不会得到0
这..显然从大到小考虑,由模板可以知道(d[i])位置上的数的前(i)位一定都是0,(因为它已经异或掉了,那..所以任意几个元素异或都不会为0...
应用
插入
(就是上面的模板)
求异或的最大值((query)_(max))
inline int query_max () {
int res=0;
for (int i=63;i>=0;i--)
if ((res^d[i])>res) res^=d[i];
return res;
}
这个贪心为什么是对的呢?
因为我们贪心让它高位尽量为1
求能异或出不同值的个数((size))
int sz () {
int res=0;
for (int i=30;i>=0;i--)
if (d[i]) res++;
return res;
}
合并(x)与(y)两个元素((combine))
node combine (node x,node y) {//x,y是两个放了线性基的结构体
for (int i=30;i>=0;i--)
if (y.d[i]) x.insert(y.d[i]);
return x;
}
求最小值((query)_(min))
int query_min () {
for (int i=0;i<=63;i++)
if (d[i]) return d[i];
}
判断一个数是否能被当前的线性基异或得到
显然判断它是否能插入线性基就好
求在所有线性基能异或出来的数中第(k)小的数((k)_(th))
先对线性基预处理,对于每一个(d[i])枚举(j=0 to i-1),如果(d[i])的第(j)位为1,就把它跟(d[j]),这样异或下来就能保证(d[i])一定只有一个1且在第(i)位上。
那么求第(k)大就可以把(k)拆成二进制数,如果(k)的(i)位上有1就把它异或(d[i]),得到的数就是第(k)大的。
void init () {
for(int i=0;i<=63;i++)
for(int j=0;j<i;j++)
if(d[i]&(1ll<<j))d[i]^=d[j];
}
int k_th (int k) {
if(k==1&&sz<n)return 0;//假如k=1,并且原来的序列可以异或出0,就要返回0,sz表示线性基中的元素个数,n表示序列长度
if(sz<n)k--;//去掉0的情况,线性基中只能异或出不为0的解
init();
int ans=0;
for(int i=0;i<=63;i++)
if(d[i]!=0){
if(k&1)ans^=d[i];
k>>=1;
}
return ans;
}
参考资料
一篇博客里面还有删除操作