Loading

线性基入门

线性基入门

线性基用来解决竞赛中关于子集异或的一类问题。

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;
}
posted @ 2021-07-05 21:52  hyl天梦  阅读(73)  评论(0编辑  收藏  举报