线性基

学了两天才学明白线性基,太痛苦了!


前置芝士:

  1. \(a \oplus b \oplus c=0\),则 \(a \oplus b=c\)

  2. \(a \oplus b=c\),则 \(a \oplus c=b\)

证明:

  1. 由于 \(c \oplus c=0\),则 \((a \oplus b)\oplus c=0=c \oplus c\),因此 \(a \oplus b=c\)

  2. 由于 \(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\) 有不可以插入线性基和可以插入线性基两种情况。

  1. 不可以插入线性基:由性质2得,\(x=d_a \oplus d_b \oplus d_c \oplus...\),成立。

  2. 可以插入线性基:令 \(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:

  1. \(S\) 中的所有元素都可以插入线性基:不管用什么顺序将序列里的数插入线性基,线性基中的元素一定与原序列元素数量相同,成立。

  2. \(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;
}
posted @ 2023-07-08 10:01  reclusive2007  阅读(39)  评论(0)    收藏  举报