线性基 学习笔记

· · 个人记录

1. 线性空间的定义

线性空间(linear space. a.k.a. 向量空间,vector space)是一个关于向量加法和标量乘法的封闭向量集合。

注意:这里的向量加法和标量乘法可以是广义的,如 OI 中广泛地将向量加法定义为异或。但它们需要满足交换律、结合律、分配律。

e.g.:\forall n\in \mathbb N_+,R^n 是线性空间。(R^n 定义为所有的 n 维向量所构成的集合)

给定向量 \bold a_{1\cdots k},若向量 \bold b 能通过它们由向量加法和标量乘法得到,那么称 \bold b 能被 \bold a_{1\cdots k} 表出(linear expression)。所有能被 \bold a_{1\cdots k} 表出的向量构成一个线性空间,\bold a_{1\cdots k} 是它的生成子集(generating subset)。

e.g.:若有 \bold a_1=(2,3),\bold a_2=(1,4),则 \bold b=(4,11)=\bold a_1+2\bold a_2 可被二者表出。

若对于线性空间中的一些向量 \bold a_{1\cdots n},\exists k_{1\cdots n}\in \mathbb R,且 \exists 1\le i\le n,k_i\ne 0,满足:

\sum\limits_{i=1}^n k_i\bold a_i=0

则称它们线性相关(linearly dependent),否则它们线性无关(linearly independent)。(此处定义和蓝书不同,下文将说明两种定义是一致的)

线性无关的生成子集被称为线性空间的基底(basis),简称基(a.k.a. 线性基)。 基的大小成为线性空间的维数(dimension)。

e.g.:\forall n\in \mathbb N_+,R_n 的一个基为 \{(1,0,\cdots,0),(0,1,\cdots,0),\cdots,(0,0,\cdots,1)\},维数为 n,可记为 \dim R_n=n。

对一个 n​​ 行 m​​ 列的矩阵 A​​,以其 n​​ 个行向量为生成子集张成的线性空间的维数称为行秩,同样我们可以定义列秩。它们统称为秩(rank),记为 r(A)​。

2. 线性空间的基本性质

证明:由封闭性易证。(简言之,线性空间存在两种运算、四个元)

证明:由空间的方程和线性空间的定义显然。

证明:考虑两组基 \{\bold v_1,\bold v_2,\cdots,\bold v_n\},\{\bold w_1,\bold w_2,\cdots,\bold w_m\}。假设 n\ne m,由:

\begin{cases} \bold w_1=&a_{1,1}\bold v_1+&a_{1,2}\bold v_2+\cdots+&a_{1,n}\bold v_n\\ \bold w_2=&a_{2,1}\bold v_1+&a_{2,2}\bold v_2+\cdots+&a_{2,n}\bold v_n\\ \vdots&\vdots&\vdots&\vdots\\ \bold w_m=&a_{m,1}\bold v_1+&a_{m,2}\bold v_2+\cdots+&a_{m,n}\bold v_n\\ \end{cases}

可得 \bold w=\bold v A,其中 A 为所有的 a 所构成的矩阵。同理有 \bold v=\bold wB。分别展开,有 \bold w=\bold wAB,\bold v=\bold vAB

由于 \bold w 和 \bold v 均线性无关,分别可得 AB=I_n,AB=I_m,产生矛盾。

综上,确定的线性空间维数唯一。

证明:类似高斯消元法易知。

证明:必要性:把定义式抄过来移项,将其中一个的系数除过去就能构造一组表示。充分性:把表达式移项,显然成立。

(也称“部分相关,总体相关;总体无关,部分无关”)

证明:由上一条性质易证。

证明:将行空间进行行变换得到 V_0,有 \dim C(A)=\dim V_0。同理有 \dim C(A^T)=\dim V_0。

可得 r(C(A))=r(C(A^T))​。(C(A)​ 表示 A​ 的行空间,C(A^T)​ 表示 A 的列空间)

证明:由于 C(AB) 是 C(A) 的一个子空间,因此有 r(C(AB))\le r(C(A))。(乘法的定义)

同理 r(C(A^TB^T))\le r(C(B^T)),r(AB)\le \min\{r(A),r(B)\}。

3. OI 中的线性基

在 OI 中,我们把每个数根据其二进制写为一个向量,以这些数为生成子集张成一个线性空间。

有线性基的定义及性质,可以轻易地导出以下结论:(摘自 OI-Wiki)

OI 中的线性基一般用于解决异或相关问题。

那么,什么时候用 01Trie 呢?一般来说,确定个数(一般为两个)的异或值考虑 01Trie,不确定个数的异或值考虑线性基。

4. 线性基的计算

区别于上文讲的二进制意义下的线性基,这节里指的都是一般的线性基。(当然二进制意义下的线性基也可这么计算)

这样的线性基可以采用高斯消元的方式计算,我们一般采用行空间进行求解。

具体地,对于每一维,我们寻找当前此维值最大的一行,并指定其为线性基的一部分,进行消元:

fo(i,1,m) {
    int cur=i;
    fo(j,i+1,n)
        if(fabs(a[cur][i])<fabs(a[j][i]))
            cur=j;
    fo(j,1,m) swap(a[i][j],a[cur][j]);
    if(fabs(a[i][i])<=eps) continue;
    fo(j,1,n) if(j!=i) {
        db v=a[j][i]/a[i][i];
        fo(k,1,m) a[j][k]-=a[i][k]*v;
    }
}

消完元我们再遍历一遍所有的行,如果此行内所有的值都是 0,则由高斯消元只执行初等行变换可知此向量可被线性基中的数表出,反之则是线性基。

int cnt=0;
fo(i,1,n) {
    bool flag=false;
    fo(j,1,m) if(fabs(a[i][j])>eps) {
        flag=true; break;
    }
    if(flag) cnt++;
}

上面代码中的cnt就是矩阵的秩。换言之,我们可以用高斯消元在 \Theta(n^3)​ 内求出矩阵的秩。

如果要用高斯消元法求二进制意义下的线性基,消元的那个部分等价于整数的异或,这意味着它的复杂度此时是 \Theta(n^2) 的。

5. 线性基的维护

对线性基的计算还有一种方式,就是采用每次动态地把数加入线性基的方式。

在 OI 中,我们一般默认线性基中任意两个元素的二进制最高位不同。若不然,可以将它们异或得到一个新数取代掉其中一个,这是等效的。

因此,我们可以对每个二进制位记录一个 cur_i,表示当前线性基中最高位为第 i 位的数。

要插入一个数 x 时,我们可以从大到小遍历每个二进制位:

void insert(ll x) {
    fr(i,55,0) {
        if((x&(1LL<<i))==0)
            continue;
        if(cur[i]==0) {
            cur[i]=x; break;
        }
        x^=cur[i];
    } 
}

如果要查询一个数 x 是否在线性空间内,可以采用与插入类似的方法,从大到小遍历每个二进制位。

如果当前位为 1 且 cur_i=0,说明之后也肯定没有元素能抵消掉这一位,直接判false。

否则就用 cur_i 去异或 x,因为我们没有别的元素可以抵消这一位。

若程序运行到尾部,说明此时 x 上的每一位都被抵消,返回true。

bool query(ll x) {
    fr(i,55,0) {
        if((x&(1LL<<i))==0)
            continue;
        if(cur[i]==0) return false;
        x^=cur[i];
    }
    return true;
}

总复杂度 \Theta(n\log x)​。一般来说,这种做法优于高斯消元法,但只能支持二进制意义下的线性基。

6. 例题

板子题,直接把插入式线性基放上。那么怎么求最大值呢?

我们还是贪心地从大到小尝试异或上 cur_i 的每一项。

fr(i,55,0) ans=max(ans,ans^cur[i]);

Code

也是板子,把高斯消元式线性基放上。

还有就是要注意精度 eps 不能开太小,否则会有精度误差。我开了 10^{-4} 就过了。

Code

这题是经典题,有两种做法。

  1. 蓝书上采用了高斯消元的方法。先一遍消元求出秩与线性基,然后贪心地加入答案。

    不过复杂度高达 \Theta(n^2\log a_i)​,这题显然超时。

  2. 在网上搜到的做法,采用插入式线性基。

那么怎么保证 k 大呢?还是采取原先的思想,如果当前答案异或上 cur_i 会使答案变大,那么就认为当前分支点中较大的一半是选择异或当前值的,反之同理。

这样我们采用类似 BST 找 k 大时的操作即可。(不过要注意这里的第 i 位并不一定是第 i 层的分支点,需要先预处理一个 pos_i)

fr(i,55,0) if(cur[i]>0)
{
    if(res<(res^cur[i])) {
        if(k>=(1LL<<pos[i]))
            k-=(1LL<<pos[i]), res^=cur[i];
    } else {
        if(k>=(1LL<<pos[i])) k-=(1LL<<pos[i]);
        else res^=cur[i];
    }
}

最后要注意特判一个 0。

Code

和板子也没差多少,高斯消元求一个线性基即可。

千万要注意消元时循环起始点的位置不一定是 i​​​,而是当前求出的秩的大小加一!参考实现如下:

fo(i,0,62) {
    int cur=-1;
    fo(j,cnt+1,n) if(a[j]&(1LL<<i))
        if(cur==-1||b[j]>b[cur])
            cur=j;
    if(cur==-1) continue;
    cnt++;
    swap(a[cnt],a[cur]), swap(b[cnt],b[cur]);
    ans+=b[cnt];
    fo(j,1,n) if(j!=cnt) if(a[j]&(1LL<<i))
        a[j]^=a[cnt];
}

Code

回顾一下 nim 游戏取胜的条件:后手必胜当且仅当当前所有堆中的个数的异或和为 0。

那么在这道题中,我们应当尽可能地让后手在第二回合取完以后异或和不为 0,这样先手才能必胜。

那么先手怎么取才能让后手在第二回合取完以后异或和一定不为 0​ 呢?我们发现第二回合后的集合就是第一回合后的集合​的一个子集,换言之,我们要保证第一回合后的集合的任意一个子集异或和均不为零。

这就说明以第一回合后的集合是线性无关的,即它是一个线性基。

这样就变成了一个线性基板子题。一般来说,线性基在博弈论中的出现往往是 nim 游戏引出的。

Code

显然这题如果用 01trie 做空间、时间都会爆炸,可以用线性基来减小规模。

我刚开始的一个想法是像最短路那样去跑,但是这样可能会把两条从 1 到 i 的路径异或起来算成答案。

正确的做法应该是很神仙的,是通过扩展仙人掌上的做法得到正解。

具体地,我们找出一条从 1 到 n 的路径,然后用所有的环去增广它。由于从路径到环的一段会被走过两遍,因此我们没有必要算出这个距离。

那么怎么找环呢?我们 dfs 整棵树,对所有的返祖边、横叉边暴力统计即可。

但是,这张图不是仙人掌,为什么这样是对的呢?实际上,由于异或的性质,我们可以通过异或得到所有的简单环。

有一个注意点是从 1 到 n 的路径是必选的,不能和环的权值一样被加入线性基。

时间复杂度 \Theta(m\log D)。

Code

事实上,如果求的不是最大值,而是对点对 (u,v) 求所有路径的异或和的加和,则用拆位处理贡献的思路。易证若线性基中此位为 1,则所有点对的加和均相等。

见CF724G Xor-matic Number of the Graph。

线性基显然没有办法满足区间可减性,但是可以实现快速合并,因此可以直接用线段树搞。

不过这个区间xor操作有点难搞,Lazy-Tag 也难传。基于这东西是区间可减的,我们可以大力差分。

令 b_i=a_i \operatorname{xor} a_{i-1},单点修改就是改 b_l 和 b_{r+1},询问的话,由于 a_i=\operatorname{xor}_{j=1}^i b_i,因此:

异或意义下,\{a_{[l,r]}\}​​ 与 \{a_l\}\cup \{b_{[l+1,r]}\}​​ 全等。​​​(因为异或可以把一些 b_i​ 消去)

这样就好了(我可能做了个假的Ynoi)。时间复杂度 \Theta(n\log n\log^2 a_i)。

Code(一发AC啊啊啊啊啊啊啊!!!)

是一道 CF 风格的构造题,也是黑题 掉紫了(大悲),非常神仙。(推完必要性发现不会做,看了题解awa)

我们先尝试求这个 x 的值,再构造排列。

首先,其实相邻两数的异或值的本质就是差分,倒着转化为前缀和,那么相当于每个数就是由这些数其中的一些异或而成的。

这说明,每个数都在由这 n 个数张成的异或空间内。那么我们求出这 n 个数的线性基,枚举 x 再逐一判断 [0,2^x-1] 中的数,是否就行了呢?

hack:

2
4 5

按照上面的这个算法,x 的值为 1。然而我们其实没法构造一个长为 2 的排列。

问题正出在数的大小上:容易知道,如果这两个数是 0 1,那么它就合法了。我们发现,对于一个 [0,2^x-1] 的排列,相邻两项的异或值小于 2^x。

那么我们把这 n 个数排序,依次加入线性基中即可。这是对必要性的说明。

那么如何构造呢?根据线性基的性质,[0,2^x-1] 中的每一个数都对应线性基的一个子集,且这些子集两两不同。

那么我们可以类似状压,用 x 位二进制数对应线性基中的每个数是否被选。

由于前缀的性质,答案排列中相邻两位所对应的二进制至多相差一位。

这其实就是格雷码!也就是说,我们直接生成几个格雷码就行了。

最后要注意,这里线性基中的数不是 cur 数组中存的数,而是插入的原始值。

Code

完结撒花~~