淺談演算法——線性基
線性基是競賽中常用來解決子集異或一類題目的演算法。(摘自百度百科)
線性基是一個整數序列的特殊集合,每個序列都至少存在一個線性基,同一個序列可以擁有多個不同的線性基;反過來,一個線性基只對應一個確定的序列。
線性基存在如下三條性質:
- 原序列中任意一個數都可以由若干個線性基內的數異或得到
- 線性基內的數無法異或得到0
- 線性基內沒有重複元素,在保證性質一的前提下,線性基內的元素個數是最少的
在證明性質之前,首先看一下線性基的一種可行的構造法,基於這種構造法,能夠保證線性基的三條性質
const int N=50; ll A[N+10]; void Add(ll x){ for (int i=N;~i;i--){ if (x&(1ll<<i)){ if (A[i]) x^=A[i]; else{ A[i]=x; break; } } } }
根據其構造方法,我們可以得出\(A\)陣列的一個性質,若\(A[i]\)不為0,則\(A[i]_{(2)}\)的第\(i+1\)位必定為1,且\(A[i]_{(2)}\)的最高位即為第\(i+1\)位(證明略)
然後,我們來對其性質進行證明
性質一
對於一個數\(x\),它僅存在兩種結果,即成功插入線性基或者失敗。分情況進行討論:
如果\(x\)不能插入線性基,那麼\(x\)在最後必然變成了0,則有\(x\otimes A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=0\)(\(\otimes\)表示異或),那麼則有\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=x\)
如果\(x\)插入了線性基,記\(x\)插入的位置為\(i\),則有\(x\otimes A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=A[K_i]\),那麼則有\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]\otimes A[K_i]=x\),同樣滿足性質一
性質二
其實蠻顯然的……如果存在\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=0\),那麼就有\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_{p-1}]=A[K_p]\)
性質三
根據構造方法可得,線性基內不可能存在重複元素;我們也可以知道,線性基內元素增加,當且僅當序列中的新元素\(x\)無法被原線性基中的元素表達,除此之外,線性基內不會增加新的元素,那麼性質三證畢
然後,關於線性基的一些簡單應用
求最大值
具體而言,求一個序列中,若干個數異或和的最大值
首先構造這個序列的線性基,由於\(A\)陣列的性質——即\(A[i]_{(2)}\)的最高位必然是1,我們只需要從最高位開始貪心取數即可
for (int i=N;~i;i--) if ((Ans^A[i])>Ans) Ans^=A[i];
求最小值
如果是一個序列異或和最小值,判斷是否存在元素不能插入線性基,如果有,則答案為0,否則,答案與線性基內異或和最小值相同,即為最小的\(A[i]\)
求第K小的值
具體而言,求一個序列,若干個數異或和的第K小值
首先對線性基進行處理,對於一個\(A[i]_{(2)}\),若其第\(j\)位為1,則\(A[i]=A[i]\otimes A[j-1]\),這樣處理後,線性基依舊對應同一個序列,即處理前與處理後的線性基為同一序列的不同線性基,證明略
處理完後,線性基內的元素大致如下(已轉成二進位制,x表示0或1)
1x0xx0x0
1xx0x0
1x0
1
若\(K_{(2)}\)的第\(x\)位為1,則\(Ans=Ans\otimes A[B_x]\),記\(A\)陣列中非零元素個數為\(n\),則\(B\)陣列滿足\(A[B_1]>A[B_2]>...>A[B_n]>0\)
ll k_th(int k){
ll Ans=0;
for (int i=N;~i;i--){
if (A[i]){
if (k&1) Ans^=A[i];
k>>=1;
}
}
}
特殊情況請自行處理(如K=0或K=1等)
證明?在更改完線性基後,\(A[B_x]\)的作用為提供其最高位的1,故按\(K_{(2)}\)中1的位置,來對應相應的\(A[B_x]\),答案即為第K小的(具體過程略)
判斷一個數能否被線性基中的數異或得到
建構函式新增返回值即可
刪除
咕咕咕,之後填坑
模板題:P3812 【模板】線性基
Description
給定n個整數(數字可能重複),求在這些數中選取任意個,使得他們的異或和最大。
Input
第一行一個數n,表示元素個數
接下來一行n個數
Output
僅一行,表示答案。
Sample Input
2
1 1
Sample Output
1
HINT
\(1\leqslant n\leqslant 50,0\leqslant S_i\leqslant 2^{50}\)
/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline char gc(){
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>inline T frd(T x){
int f=1; char ch=gc();
for (;ch<'0'||ch>'9';ch=gc()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=gc()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
template<typename T>inline T read(T x){
int f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x){
if (x<0) putchar('-'),x=-x;
if (x>9) print(x/10);
putchar(x%10+'0');
}
template<typename T>inline T min(T x,T y){return x<y?x:y;}
template<typename T>inline T max(T x,T y){return x>y?x:y;}
template<typename T>inline T Swap(T &x,T &y){T t=x; x=y,y=t;}
const int N=50;
ll A[N+10];
void Add(ll x){
for (int i=N;~i;i--){
if (x&(1ll<<i)){
if (A[i]) x^=A[i];
else{
A[i]=x;
break;
}
}
}
}
int main(){
int n=read(0); ll Ans=0;
for (int i=1;i<=n;i++) Add(read(0ll));
for (int i=N;~i;i--) if ((Ans^A[i])>Ans) Ans^=A[i];
printf("%lld\n",Ans);
return 0;
}