線性基入門
線性基入門
線性基用來解決競賽中關於子集異或的一類問題。
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\)
程式碼需要判段該位是否為 \(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;
}