线性基学习笔记

线性基很好理解,可以理解成 \(n\) 维的向量。

我们先考虑 \(n=2\),这是我们最熟悉的,可以在平面直角坐标系上表示出来。

众所周知,在一个平面内,两个不共线的向量 \(e_1\)\(e_2\),可作为基底,即所有可在原坐标系上表示的向量 \(x\) 均可被 \(e_1\)\(e_2\) 表示为 \(\lambda e_1 + \mu e_2 (\lambda,\mu \in R)\)\(n\) 维向量以此类推。

而这个基底,也就是


这是一种很肤浅的理解,但我们也算是对线性基有了一些了解。接下来就是较为具体的叙述。

基(也称为基底)是描述、刻画向量空间的基本工具,刚刚的 \(e_1\)\(e_2\) 就可以将“所有向量”表示出来,这个 “所有向量”,就是向量空间,只不过这个向量空间是无穷大的而已。

那如果给你一个有限的向量空间 \(S\),那么基就是 \(e=\left\{ e_1,e_2,...,e_k \right\}\),其中满足这组基能够表示这个向量空间 \(S\),那也就可以表示成 $\mathrm{span}(e) $,那么这组基需要满足 \(\forall_i,e_i \notin \mathrm{span}(\frac{e}{e_i})\)(即互相不能表示),而且,根据上文,容易发现 \(n\) 维向量空间的基最多有 \(n\) 个。


接下来就是如何求出一组基

所以对于现在的基的集合,我们记为 \(E\),我们枚举当前向量 \(x=(a_1,a_2,...,a_n)\),看是否在 \(\mathrm{span}(E)\) 中。

这其实就是一个消元的过程,我们先令 \(E\) 中的元素 \(E_i\) 为第 \(1\) 个非 \(0\) 的位置是 \(i\) 的向量,然后我们按维数从低往高考虑,尝试将当前维删到 \(0\) 即可,若当前维不存在基,那就将现在的向量加入 \(E\) 中。


OI 中用的最多的是异或线性基,以下是一些应用:

1.插入操作

点击查看代码
void insert(int x){
	for(int i=N-1;i>=0;i--){
		int p=x>>i;
		if(!p) continue;
		if(!bas[i]){
			bas[i]=x;
			s++;
		}
		x^=bas[i];
	}
}

2.最大异或和 & 第 \(k\) 大异或和

最大异或和是简单的,从高位往低位考虑即可

点击查看代码
int qmax(){
	int res=0;
	for(int i=N-1;i>=0;i--) res=max(res,res^bas[i]);
}

\(k\) 大只需稍作修改,不再赘述


3.线性基合并

这个也是容易的,就是将其中一个线性基中的基拿出来 insert 到另外一个即可。

复杂度 \(log^2(V)\)。(\(V\) 是值域)


4.线性基求交

求交的意思是求两个线性基的线性空间(可以想成向量空间) \(V_a = \mathrm{span}(a)\)\(V_b = \mathrm{span}(b)\) 的交的基。

发现不太好直接通过线性基 \(a\)\(b\) 直接求,但我们容易知道 \(b\) 中有那些基是能够被 \(a\) 表示出来的,形式化来讲,就是 \(U = \left \{i|b_i \in V_a \right \}\)。那么显然有 \(\mathrm{span}(U) \subseteq V_a \wedge V_b\)

那如果 \(a \cup \frac{b}{U}\) 线性无关,就有 \(\mathrm{span}(U) = V_a \wedge V_b\)

证明:考虑有一个值 \(v\) 不能被 \(U\) 表示,但是在 \(V_a \wedge V_b\) 中。那么 \(\frac{b}{U}\) 中的元素一定会参与,设参与了的值的异或和为 \(t\),那么显然 \(v \oplus t\) 能被 \(a\) 表示出来,因为 \(U\) 中的值均在 \(V_a\) 中,但是 \(a\) 本身就能表示 \(v\),而 \(a \cup \frac{b}{U}\) 也可以表示出 \(v\),且两种表示方法显然不同,但我们又知道一个值在一个线性无关组中并不存在 \(2\) 种不同的表示方法,\(Q.E.D\)

所以我们只需把 \(a \cup \frac{b}{U}\) 给调整成线性无关的即可。也就是说找到一组合适的 \(b\) 满足以上条件。

首先先有一个直接按照上面所说的,枚举当前 \(b_i\),看当前 \(a \cup \frac{b}{U}\) 是否能将 \(b_i\) 表示出来,如果不可以,就将其加入 \(\frac{b}{U}\)。不然,我们设 \(x\) 为参与表示 \(b_i\)\(a\) 中的元素的异或和,我们将 \(x\) 加入 \(U\) 中。

但这个复杂度是 \(O(log^3 (V))\) 的,并不是很理想。

所以我们动态维护 \(a \cup \frac{b}{U}\) 的三角线性基(也就是上面的 insert),在维护基的基础上,再维护一个组成当前元素在 \(a\) 的部分的异或和,这样就可以在 \(O(log^2(V))\) 的时间复杂度内实现了

点击查看代码
int V=63;
node wedge(node X,node Y){
    for(int i=0;i<=V;i++){
        tmpb[i]=tmpc[i]=X.b[i],X.b[i]=0;
    }
    for(int i=V;i>=0;i--){
        if(!Y.b[i]) continue;
        ull x=Y.b[i],y=0;
        for(int j=V;j>=0;j--){
            if((x>>j)&1ull){
                if(!tmpb[j]){
                    tmpb[j]=x,tmpc[j]=y;
                    break;
                }
                x^=tmpb[j],y^=tmpc[j];
            }
        }
        if(!x) X.insert(y);
    }
    return X;
}


5.找到线性基能表示的数中比 \(x\) 大的最小值

首先先求得最大值,接着按位从大到小枚举,如果当前位为 \(1\),那就看异或上这个位置的基是否还比 \(x\) 大,如果仍比 \(x\) 大,那就异或上这个基。

点击查看代码
int gt_greater(int x){
	int val=0;
	for(int i=29;i>=0;i--){
		if(!b[i]) continue;
		val=max(val,val^b[i]);
	}
	for(int i=29;i>=0;i--){
		if(!b[i]) continue;
		if(val>>i&1){
			if((val^b[i])>x) val^=b[i];
		}
	}
	return val;
}
posted @ 2025-01-02 15:51  ~Cyan~  阅读(99)  评论(0)    收藏  举报