1. 程式人生 > 其它 >線性基入門

線性基入門

線性基入門

線性基用來解決競賽中關於子集異或的一類問題。

1 定義

  • 異或和

    \(S\) 為無符號整數集,(若不說明,下文所有集合均指無符號)那麼集合 \(S\) 的異或和是 \(xor\_sum(S)=s_1\hat{}s_2...s_{|S|}\)

  • 長成

    \(T\subseteq S\) ,則所有的 \(sor\_sum(T)\) 組成的子集稱為 \(S\) 的長成,記作 \(span(S)\)

  • 線性相關

    如果存在一個 \(S_j\) ,用 \(S\) 中其他的一些元素可以異或得到,或者等價的說,\(S_j\in span(S-S_j)\) ,那麼稱 \(S\) 線性相關,否則線性無關。

  • 我們稱集合 \(B\)\(S\) 的線性基當且僅當滿足:

    • \(S\subseteq span(B)\) ,即 \(S\)\(B\) 張成的子集。
    • \(B\) 線性無關。
    • \(B\)極小的滿足上述性質的集合。

2 性質

  • 線性基的任何真子集都不可能是線性基。
  • \(S\) 中的任意元素都可以唯一的表示為 \(B\) 中若干元素異或起來的結果。

3 構造

問題是給定一個子集,要求求其線性基,下面給出構造程式碼。

    ll a[60];
    inline void insert(ll x){
        for(int i=55;i>=0;i--){
            if(!(x&(1ll<<i))) continue;
            if(!a[i]){a[i]=x;break;}else x^=a[i];
        }
    }

如果 \(a_i\) 不為 \(0\) ,那麼 \(a_i\) 上的數的二進位制最高位必定為第 \(i\) 位,這個性質說明了這個線性基線性無關。

顯然,異或運算是可逆的,重新異或一遍就可以得到原來的數,所以這個線性基的長成子集包含 \(S\)

綜上,可以知道上述構造方案是正確的。

還有一種構造方案可以使構造出的線性基有一個特殊的性質:

  • 如果 \(a_i\) 不為 \(0\) ,那麼除了 \(i\) 之外的所有 \(j\) 滿足 \(a_j\)\(i\) 位不為 \(1\)

我們只需要在加入的時候列舉 \(j<i\) 的所有 \(a_j\) ,用 \(x\) 去異或( \(x\)

是我們插入的數)即可滿足 \(x\) 的第 \(j\) 位沒有這些數。同時我們列舉 \(j>i\) 的所有 \(a_j\) ,用他們異或 \(x\) ,即可滿足後面的數不會有 \(x\)

程式碼需要判段該位是否為 \(1\)

程式碼:

    ll a[60];
    inline void insert(ll x){
        for(int i=55;i>=0;i--){
            if(!(x&(1ll<<i))) continue;
            if(!a[i]){
                for(int j=i-1;j>=0;j--) if(x&(1ll<<j)) x^=a[j];
                for(int j=i+1;j<=55;j++) if(a[j]&(1ll<<i)) a[j]^=x;
                a[i]=x;break;
            }else x^=a[i];
        }
    }

注意:第 \(5,6\) 行不能交換,因為在加入 \(x\) 前線性基滿足性質,如果交換順序,很可能在讓 \(a_j\) 異或 \(x\) 的時候讓 \(a_j\) 的某一位變成一從而不滿足性質,所以要先改變 \(x\)

演算法的正確性依然是根據異或的可逆性。

明顯上面的兩個構造方法單次插入都是 \(\log n\) 的。

4 求最大值

對於第一個構造方法,我們需要貪心的去求解最大值:如果異或變大就異或,因為優先異或位數大的肯定要更優。

對於第二種,因為其特殊性質,我們直接異或到底。

程式碼 \(1\)

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N number
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct xianxingji{
    ll a[60];
    inline void insert(ll x){
        for(int i=55;i>=0;i--){
            if(!(x&(1ll<<i))) continue;
            if(!a[i]){a[i]=x;break;}else x^=a[i];
        }
    }
    inline ll ask_max(){
        ll ans=0;
        for(int i=55;i>=0;i--){
            if((ans^a[i])>ans) ans=ans^a[i];
        }
        return ans;
    }
};
xianxingji xxj;

int n;

int main(){
    read(n);
    for(int i=1;i<=n;i++){
        ll x;read(x);xxj.insert(x);
    }
    printf("%lld\n",xxj.ask_max());
    return 0;
}

程式碼 \(2\)

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N number
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct xianxingji{
    ll a[60];
    inline void insert(ll x){
        for(int i=55;i>=0;i--){
            if(!(x&(1ll<<i))) continue;
            if(!a[i]){a[i]=x;break;}else x^=a[i];
        }
    }
    inline ll ask_max(){
        ll ans=0;
        for(int i=55;i>=0;i--){
            if((ans^a[i])>ans) ans=ans^a[i];
        }
        return ans;
    }
};
xianxingji xxj;

int n;

int main(){
    read(n);
    for(int i=1;i<=n;i++){
        ll x;read(x);xxj.insert(x);
    }
    printf("%lld\n",xxj.ask_max());
    return 0;
}