线性基
学了两天才学明白线性基,太痛苦了!
前置芝士:
-
若 \(a \oplus b \oplus c=0\),则 \(a \oplus b=c\)
-
若 \(a \oplus b=c\),则 \(a \oplus c=b\)
证明:
-
由于 \(c \oplus c=0\),则 \((a \oplus b)\oplus c=0=c \oplus c\),因此 \(a \oplus b=c\)
-
由于 \(a \oplus b=c\),则 \((a \oplus b)\oplus c=c \oplus c\),即 \(a \oplus b \oplus c=0\),显然异或运算是满足交换律的,因此 \(a \oplus c \oplus b=0\),由性质1得:\(a \oplus c=b\)
线性基基本概念:
定义:给定数集 \(S\),以异或运算张成的数集与 \(S\) 相同的极大线性无关集,称为原数集的一个线性基。
通俗地说,线性基是一个数的集合。每个序列都拥有至少一个线性基。取线性基中若干个数异或起来可以得到原序列中的任何一个数。
线性基的性质:
性质1:
原序列的任意一个数都可以由线性基内部的一些数异或得到。
性质2:
线性基内部的任意数异或起来都不能得到 0。
性质3:
线性基内部的数个数唯一;且在保持性质1的前提下,数的个数是最少的。
证明:(令线性基为 \(d\))
性质2:(反证法)
假设 \(d_a \oplus d_b \oplus d_c=0\),则 \(d_c=d_a \oplus d_b\)
由于 \(d_c\) 可以由 \(d_a\) 和 \(d_b\) 异或得到,因此 \(d_c\) 将不会被插入线性基。
假设不成立。
因此线性基内部的任意数异或起来都不能得到 \(0\)。
性质1:
若 \(x \in S\),则 \(x\) 有不可以插入线性基和可以插入线性基两种情况。
-
不可以插入线性基:由性质2得,\(x=d_a \oplus d_b \oplus d_c \oplus...\),成立。
-
可以插入线性基:令 \(x\) 是线性基中的第 \(i\) 位,则 \(x\)在插入前异或了线性基中的若干个数,那么 \(x \oplus d_a \oplus d_b \oplus d_c \oplus...=d_i\),则 \(x=d_a \oplus d_b \oplus d_c \oplus...\oplus d_i\),成立。
综上,原序列的任意一个数都可以由线性基内部的一些数异或得到。
性质3:
-
若 \(S\) 中的所有元素都可以插入线性基:不管用什么顺序将序列里的数插入线性基,线性基中的元素一定与原序列元素数量相同,成立。
-
若 \(S\) 中有部分元素不可以插入线性基:假设 \(x\) 不可以插入线性基,且 \(x \oplus d_a \oplus d_b \oplus d_c=0\),那么 \(x=d_a \oplus d_b \oplus d_c\),尝试将插入顺序改为 \(d_a,d_b,x,d_c\),则 \(d_c=d_a \oplus d_b \oplus x\),那么 \(d_c\) 将不会被插入线性基,那么线性基元素个数还是不变的。若去掉线性基里面的任一个数,都会使得原序列里的数无法通过用线性基里的元素异或得到,没有多余的元素。所以线性基的元素个数在保持性质一的前提下,一定是最少的。成立。
线性基的基本操作:
插入:
令插入的数为 \(x\),考虑 \(x\) 的二进制 \(1\) 最高位为 \(i\),
若线性基的第 \(i\) 位为 \(0\),则直接在该位插入 \(x\),返回。
若线性基的第 \(i\) 位已经有值,就把 \(x\) 和其异或。
很容易想到,这样得出的线性基 \(d_i\) 的 \(1\) 最高位为 \(i\) 的异或和。
这样插入,可以得到线性基的异或和的值域与原数列的异或和值域相同。
时间复杂度:\(O(\log n)\)(\(n\) 为值域,下同)
void insert(LL x){
for(int i=M;i>=0;i--){
if(x&(1LL<<i)){
if(!a[i]){a[i]=x;return;}
else x^=a[i];
}
}
flag=1;
return;
}
求最大值:
如果当前的 \(ans\) 异或上 \(d_i\) 可以变得更大,那就用 \(ans \oplus d_i\) 来更新 \(ans\)。
时间复杂度:\(O(\log n)\)
LL mymax(){
LL ans=0;
for(int i=M;i>=0;i--)
ans=max(ans,ans^a[i]);
return ans;
}
求最小值:
线性基中最小的 \(d_i\) 就是答案,因为 \(d_i\) 最小,异或上线性基其他的数只会变得更大。
时间复杂度:\(O(\log n)\)
LL mymin(){
if(flag)return 0;
for(int i=0;i<=M;i++)
if(a[i])return a[i];
}
查询 \(x\) 是否在值域中:
时间复杂度:\(O(\log n)\)
和插入操作一样。
bool check(LL x){
for(int i=M;i>=0;i--)
if(x&(1LL<<i))
if(!a[i])return false;
else x^=a[i];
return true;
}
重构线性基:
原来线性基中的 \(d_i\) 的 \(0\)~\(i-1\) 位不一定都是 \(0\),因此让 \(d_i\) 只有第 \(i\) 位是 \(1\),其余都是 \(0\),方便求第 \(k\) 小的值。
重构后的线性基:
0 ... 0 1
0 ... 1
...
0 1
1
原来的 \(d_i\) 最高位一定是 \(1\),\(d_j\) 最高位一定是 \(1\),如果 \(d_i\) 的第 \(j\) 位是 \(1\),那 \(d_i \oplus d_j\) 后第 \(j\) 位的 \(1\) 就会变为 \(0\)。
void rebuild(){
cnt=0;
for(int i=0;i<=M;i++){
for(int j=i-1;j>=0;j--){
if(a[i]&(1LL<<j))a[i]^=a[j];
}
if(a[i])d[cnt++]=a[i];
}
}
查询第 \(k\) 小的值:
重构线性基后,显然线性基可以表示出 \(2^{cnt}\) 个数,若 \(k\) 比 \(2^{cnt}\) 还大,那一定表示不了。
剩下的跟插入差不多。
时间复杂度:\(O(log^{2} n)\)(要重构线性基)
LL query(LL k){
k-=flag;//减去0的情况
if(!k)return 0;
if(k>=(1LL<<cnt))return -1;//cnt是大了一位,可以取等号
LL ans=0;
for(int i=0;i<cnt;i++){
if(k&(1LL<<i))ans^=d[i];
}
return ans;
}

浙公网安备 33010602011771号