1. 程式人生 > 實用技巧 >淺談演算法——線性基

淺談演算法——線性基

線性基是競賽中常用來解決子集異或一類題目的演算法。(摘自百度百科)


線性基是一個整數序列的特殊集合,每個序列都至少存在一個線性基,同一個序列可以擁有多個不同的線性基;反過來,一個線性基只對應一個確定的序列。

線性基存在如下三條性質

  • 原序列中任意一個數都可以由若干個線性基內的數異或得到
  • 線性基內的數無法異或得到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;
}