線性基

知識預備

線性基詳解
線性基學習筆記

線性基的定義

由原集合AA得到線性基TT
使得TT中元素互相異或所形成的集合,等價於原序列AA的元素互相異或形成的集合
可以理解爲線性基將原序列進行了壓縮

線性基的性質

  1. 線性基能相互異或得到原集合的所有相互異或得到的值
  2. 線性基是滿足性質11的最小的集合
  3. 線性基沒有異或和爲00的子集

線性基的構造

設一個數組dd,表示序列aa的線性基,下標從00開始
d[i]表示的是線性基中第i+1i+1個位置上所存儲的數字
如果這個數字不爲00
那麼這個數字轉化爲二進制數後的第i+1i+1位是11
且這個二進制數的最高位是第i+1i+1
我們都將序列aa中每一個數依次插入線性基中,得到了序列aa的線性基
下面是向線性基中插入一個數的代碼

bool insert(ll val){//向線性基中插入一個數
	for(ll i=59;i>=0;i--){
		if(val&&1ll<<i){
			if(d[i])val^=d[i];
			else{
				d[i]=val;
				break;
			}
		}
	}
	return val>0;//判斷val是否插入成功
}

我們可以看到,在線性基中插入數valval時,從高位到低位依次掃描它爲11的二進制位
當掃描到第i位時,如果d[i]d[i]不存在,就令d[i]=vald[i]=val,插入結束
如果d[i]存在,此時valvald[i]d[i]的第i+1i+1位都爲11,就令valval=valval^d[i]d[i]
於是valvald[i]d[i]異或後第i+1i+1位變成了00,然後繼續向下掃描
最終,valval會有插入成功和插入不成功這兩種結局
如果插入成功,就說明當前線性基裏的一些數異或起來不能等於valval,因此valval是不可替代的
如果插入不成功,是因爲當前線性基裏的一些數異或起來可以等於valval
並且valval在一系列操作之後變成了00,因此valval是多餘的

查詢最大異或和

準確地說,是求一個序列中的若干個數的異或和的最大值
從高位到低位掃描線性基,如果答案異或d[i]d[i]後變大了,就讓答案異或d[i]d[i]
這其實是個貪心的過程,我們只需讓答案的高位儘可能大
當掃到d[i]d[i]d[i]d[i]不爲00
由於d[i]d[i]的第i+1i+1位是11,且d[i]的i+1i+1位以上都是00
所以如果答案的第i+1i+1位是00,則答案異或上d[i]d[i]之後一定會變大
如果答案的第i+1i+1位是11,則答案異或上d[i]d[i]之後一定會變小

ll query_max(){
	ll ans=0;
	for(ll i=59;i>=0;i--)
		if(ans^d[i]>ans)
			ans^=d[i];
	return ans;
}

查詢最小異或和

最小值其實就是最小的d[i]d[i]
這是因爲如果讓最小的d[i]d[i]去異或其它的d[i]d[i],那麼它一定會變大,所以它自己就是最小的
顯然,如果這個線性基有無法插入的數,那麼最小異或和就爲00

查詢第k小異或和

準確地說:從一個序列中取任意個元素進行異或,求能異或出的所有數字中第kk小的值。
要求第kk小值,首先將線性基進行改造,改造後每一個d[i]d[i]相互獨立
對於每一個d[i]d[i],枚舉jj=ii to 11,如果d[i]d[i]的第jj位爲1,那麼讓d[i]d[i]異或d[j1]d[j−1]
這樣改造後,如果d[i]d[i]不爲00,那麼所有d[j]d[j]的第i+1i+1位上爲11的只有d[i]d[i]
於是線性基中的元素,作用其實都是提供自己最高位上的11
那麼只要使提供出來的11可以和kk的每一位上的11對應
那麼求出來的答案就是第kk小的

void rebuild(){
	for(int i=59;i>=0;i--)
		for(int j=i-1;j>=0;j--)
			if(d[i]&1ll<<j)d[i]^=d[j];
}
ll query_kth(ll k){
	rebuild();
	int cnt=0;
	for(int i=0;i<=59;i++)if(d[i])cnt++;
	if(cnt<n&&k==1)return 0;//最小異或和爲0的情況
	if(cnt<n)k--;//最小異或和爲0的情況
	if(k>=(1ll<<cnt))return -1;//不存在第k小異或和
	ll ans=0;
	for(int i=0;i<=59;i++)if(d[i]){
		if(k&1)ans^=d[i];
		k>>=1;
	}
	return ans;
}

例一:HDOJ 3949

題意

給定n(n≤10000) 個數 a1,a2,,ana_1,a_2,…,a_n​​,以及 Q(Q≤10000)個詢問,每次詢問這些數(至少一個,不能不選)能夠組成的異或和中第kk小的數是什麼(去掉重複的異或和)。

代碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
const int M=1e4+5;
int cs,n,m,cnt;
ll k,A[M],d[60];
void insert(ll x){
	for(int i=59;i>=0;i--){
		if(x&1ll<<i){
			if(d[i])x^=d[i];
			else{
				d[i]=x;
				break;
			}
		}
	}
}
void rebuild(){
	for(int i=59;i>=0;i--)
		for(int j=i-1;j>=0;j--)
			if(d[i]&1ll<<j)d[i]^=d[j];
}
ll query_kth(ll k){
	if(cnt<n&&k==1)return 0;
	if(cnt<n)k--;
	if(k>=(1ll<<cnt))return -1;
	ll ans=0;
	for(int i=0;i<=59;i++)if(d[i]){
		if(k&1)ans^=d[i];
		k>>=1;
	}
	return ans;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
//	freopen("jiedai.out","w",stdout);
#endif
	rd(cs);
	for(int cas=1;cas<=cs;cas++){
		printf("Case #%d:\n",cas);
		memset(d,0,sizeof(d));
		rd(n);
		for(int i=1;i<=n;i++)rd(A[i]),insert(A[i]);
		rd(m);
		rebuild();
		cnt=0;
		for(int i=0;i<=59;i++)if(d[i])cnt++;
		while(m--)rd(k),printf("%lld\n",query_kth(k));
	}
	return (0-0);
}

例二:HDOJ 6579

題意

有n個數,m次操作,強制在線。
操作0 l r:詢問[l,r]的最大異或和;
操作1 x:序列的最後添加一個數x。

分析

暴力的做法可以用數據結構維護區間線性基,但肯定過不了。貪心地維護序列的前綴線性基 (上三角形態),對於每個線性基,將出現位置靠右的數 字儘可能地放在高位,也就是說在插入新數字的時候,要同時記錄對應位置上數字的出現位 置,並且在找到可以插入的位置的時候,如果新數字比位置上原來的數字更靠右,就將該位 置上原來的數字向低位推。 在求最大值的時候,從高位向低位遍歷,如果該位上的數字出現在詢問中區間左端點的 右側且可以使答案變大,就異或到答案裏。 對於線性基的每一位,與它異或過的線性基更高位置上的數字肯定都出現在它右側 (否 則它就會被插入在那個位置了),因此做法的正確性顯然。

代碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
const int M=1e6+5,K=31;
int n,m,cas,A[M];
int d[M][K],id[M][K];
void build(int val,int pos){
	int tmp=pos;
	for(int i=29;i>=0;i--){
		d[pos][i]=d[pos-1][i];
		id[pos][i]=id[pos-1][i];
	}
	for(int i=29;i>=0;i--){
		if(val&1<<i){
			if(d[pos][i]){
				if(tmp>id[pos][i]){
					swap(d[pos][i],val);
					swap(id[pos][i],tmp);
				}
				val^=d[pos][i];
			}
			else{
				d[pos][i]=val;
				id[pos][i]=tmp;
				break;
			}
		}
	}
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
//	freopen("jiedai.out","w",stdout);
#endif
	rd(cas);
	while(cas--){
		rd(n),rd(m);
		for(int i=1;i<=n;i++)rd(A[i]),build(A[i],i);
		int ans=0,op,x,l,r;
		while(m--){
			rd(op);
			if(op)rd(A[++n]),build(A[n]^=ans,n);
			else{
				rd(l),rd(r);
				l=(l^ans)%n+1;
				r=(r^ans)%n+1;
				if(l>r)swap(l,r);
				ans=0;
				for(int i=29;i>=0;i--)if(id[r][i]>=l)MAX(ans,ans^d[r][i]);
				printf("%d\n",ans);
			}
		}
		for(int i=1;i<=n;i++)
			for(int j=0;j<30;j++)
				d[i][j]=id[i][j]=0;
	}
	return (0-0);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章