线性基

基,顾名思义里面的东西是用来组成需要的所有东西的基础。线性基即通过线性组合得到需要的所有东西。

严谨一点(太严谨有点奇怪)。有一组 \(k\) 维向量 \(T=\{a_1,a_2,\cdots,a_n\}\),对于值域(需要的所有向量)\(S\)\(\forall x\in S,x=t_1\cdot a_1+t_2\cdot a_2+\cdots+t_n\cdot a_n\)\(S\) 被称为数域,\(T\)\(S\) 的基,且 \((T,+)\) 是 Abel 群(对 \(+\) 封闭且满足结合律交换律,有单位元逆元),则称 \((T,+,\cdot,S)\) 为线性空间。

\(a_1,a_2,\cdots,a_n\) 两两“互质”(向量组内部没有向量可以被其他向量表出),叫做极大线性无关组,对于任意 \(R\supseteq T\)\(|T|\) 被称为 \(R\),称线性基

特别的 \(\{0\}\) 的基为 \(\varnothing\)

\(k\) 维线性空间 \(S\) 可以由 \(k\) 个单位向量线性表示。比如 \(\begin{pmatrix} 1\\ 0\\ 0 \end{pmatrix},\begin{pmatrix} 0\\ 1\\ 0 \end{pmatrix},\begin{pmatrix} 0\\ 0\\ 1 \end{pmatrix}\) 三个基可以表示三维线性空间。记 \(k\) 为线性基的元素个数。通常写作 \(\dim S\)

线性基的用处:

  1. 求某向量组的秩
  2. 求某向量组的一组基
  3. 动态插入向量
  4. 判断某向量是否能被 某向量组的一组基 表示
  5. 求线性空间的极值/特殊值 ★

用的最多的是异或线性基,它可以用来处理线性空间 \((T,\oplus,\And,B^k,B=\{0,1\})\) 的问题,即 \(k\) 维 bool 空间的问题,即每维只有 0/1 两种取值。

构造:记原组为 \(A\),设 \(P\) 为基,\(P[i]\) 表示最高位的 1 为第 \(i\) 位的一个基。

贪心地,枚举 \(i\),对于每个 \(a_i\),设 \(a_i\) 的最高位为 \(j\),则若 \(p[j]=0\),则把 \(p[j]\) 设为 \(a_i\),否则更新 \(a_i\)\(a_i\oplus p[j]\),即降低 \(j\),使 \(a_i\) 可能成为低位的基从而尽快填满基。

现在看如何实现上述用处。

  1. 直接对每个 \(j\)\(p[j]\) 是否等于 0,等于 0 说明最高位为 \(j\) 的基不存在。秩即为 \(r=\sum [p[j]\neq 0]\)
  2. 即构造。
  3. 显然构造是动态的。
  4. 设数为 \(x\)。先特判 \(x=0\),如果 \(r\) 满了则无法表示;否则若 \(x\) 的第 \(j\) 位为 1,则将 \(x\) 异或上 \(p[j]\),即消去第 \(j\) 位,直到消完,如果消不完就表示不了。
    • 极小值:如果 \(r\) 满了则为 \(p[0]\),否则为 0。
    • 极大值:记一个变量 \(x\) 为 0,从高到低枚举 \(j\)\(x\)\(j\) 位为 0 则异或上 \(p[j]\)
    • \(k\) 小/大:显然这两个问题可以互相转化。现在考虑第 \(k\) 小。对于这组基,显然可以得到 \(2^r\) 个异或值(选或不选某个 \(p[j]\)),若可以异或出 0,则将 0 设为第 1 小。考虑重构 \(P\),使得只有 \(p[j]\) 的第 \(j\) 为 1,显然可以直接异或得到。然后可以发现只有前 \(r\) 个 1 的数值会变化,显然这几位字典序排一排就可以搞出第 \(k\) 小。

板子(求最大异或和):

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+7;
int n;
int a[maxn];
int p[66];
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        for(int j=50;~j;j--){
            if(a[i]&(1ll<<j)){
                if(!p[j]){
                    p[j]=a[i];
                    break;
                }else a[i]^=p[j];
            }
        }
    }
    int x=0;
    for(int j=50;~j;j--){
        if(x&(1ll<<j)) continue;
        x^=p[j];
    }
    cout<<x;
    return 0;
}

经典例题:最大异或路径

给你一个无向图,求 \(s\)\(t\) 的路径中,边权异或和最大的那条。

先要找性质,把问题转化成线性基可以做的东西。

性质 1:将路径异或上一个环(\(n\ge 3\))相当于走了那个环。显然由于去换上和回到路径上是同一条路,边被异或掉了。

性质 2:如果走环,一定会走完。即使看起来有些环没走完,实际上都可以简化成一条路径异或上若干个环。

有了以上两个性质就可以上线性基了。暴力做法:把所有环加入线性基,随便找一条路径,然后通过线性基将异或和变大。

这是对的。考虑 DFS,当前搜到的 \(u\) 权值为 \(x\),如果 \(u\) 的邻点 \(v\) 已经被搜过,且权值为 \(y\)\((u,v)\) 权值为 \(z\),则该环的权值为 \(x\oplus y\oplus z\),时间复杂度也是对的,为 \(O((n+m)\log x)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e4+7;
int p[77];
void ins(int x){
    for(int j=62;~j;j--){
        if(x&(1ll<<j)){
            if(!p[j]){
                p[j]=x;
                return;
            }else x^=p[j];
        }
    }
}
struct edge{
    int v,w;
};
vector<edge>e[maxn];
int n,m;
int vis[maxn];
int now;
void dfs(int u){
    if (u==n) now=vis[u];
    for(edge v:e[u]){
        if(vis[v.v]==-1){
            vis[v.v]=vis[u]^v.w;
            dfs(v.v);
        }else ins(vis[u]^vis[v.v]^v.w);
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>m;
    for(int i=1,u,v,w;i<=m;i++){
        cin>>u>>v>>w;
        e[u].push_back({v,w});
        e[v].push_back({u,w});
    }
    for(int i=1;i<=n;i++) vis[i]=-1;
    vis[1]=0;
    dfs(1);
    for(int j=62;~j;j--){
        if(now&(1ll<<j)) continue;
        now^=p[j];
    }
    cout<<now;
    return 0;
}

P3292 [SCOI2016] 幸运数字

给你一棵树询问 \(u,v\) 路径上选择一些点权使得异或和最大。

线性基 + 倍增。

和 LCA 差不多,记 \(f(j,i)[k]\) 表示 \(i\)\(i\)\(2^j\) 级祖先之间的线性基。则有转移:

\[f(j,i)[k]=merge(f(j-1,i)[k],f(j-1,fa(j-1,i))[k]) \]

merge 直接暴力插入。时间复杂度 \(O(n\log n\log^2 a)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e4+7;
bool st;
int n,m,w[maxn];
vector<int>e[maxn];
int fa[16][maxn];
int f[16][maxn][63],dep[maxn];
void ins(int x,int y,int z){
    for(int j=61;~j;j--){
        if(z&(1ll<<j)){
            if(!f[x][y][j]){
                f[x][y][j]=z;
                return;
            }else z^=f[x][y][j];
        }
    }
}
void dfs(int u,int Fa){
    fa[0][u]=Fa;
    dep[u]=dep[Fa]+1;
    ins(0,u,w[u]);
    ins(0,u,w[Fa]);
    for(int v:e[u]) 
        if(v!=Fa) dfs(v,u);
}
int tmp[77];
void merge1(int x,int y){
    for(int i=61;~i;i--){
        f[x][y][i]=f[x-1][y][i];
        tmp[i]=f[x-1][fa[x-1][y]][i];
    }
    for(int i=61;~i;i--){
        for(int j=61;~j;j--){
            if(tmp[i]&(1ll<<j)){
                if(!f[x][y][j]){
                    f[x][y][j]=tmp[i];
                    break;
                }else tmp[i]^=f[x][y][j];
            }
        }
    }
}
int pmp[77];
void merge2(int x,int y){
    for(int i=61;~i;i--){
        tmp[i]=f[x][y][i];
        for(int j=61;~j;j--){
            if(tmp[i]&(1ll<<j)){
                if(!pmp[j]){
                    pmp[j]=tmp[i];
                    break;
                }else tmp[i]^=pmp[j];
            }
        }
    }
}
int result(){
    int x=0;
    for(int j=61;~j;j--){
        if(x&(1ll<<j)) continue;
        x^=pmp[j];
    }
    return x;
}
void init(){
    for(int j=1;j<=14;j++)
        for(int i=1;i<=n;i++)
            fa[j][i]=fa[j-1][fa[j-1][i]];
    for(int j=1;j<=14;j++)
        for(int i=1;i<=n;i++)
            merge1(j,i);
}
int lca(int u,int v){
    if(u==v) return w[u];
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=0;i<=61;i++) pmp[i]=0;
    int step=dep[u]-dep[v];
    for(int j=14;~j;j--){
        if(step&(1<<j)){
            merge2(j,u);
            u=fa[j][u];
        }
    }
    if(u==v) return result();
    for(int j=14;~j;j--){
        if(fa[j][u]!=fa[j][v]){
            merge2(j,u);
            merge2(j,v);
            u=fa[j][u],v=fa[j][v];
        }
    }
    merge2(0,u);
    merge2(0,v);
    return result();
}
bool ed;
signed main(){
    // cerr<<(&ed-&st)/1048576.0<<" MB\n";
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    for(int i=1,u,v;i<n;i++){
        cin>>u>>v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs(1,0);
    init();
    while(m--){
        int u,v;
        cin>>u>>v;
        cout<<lca(u,v)<<'\n';
    }
    return 0;
}
posted @ 2024-11-12 10:24  view3937  阅读(5)  评论(0编辑  收藏  举报
Title