mengxiaolong

 

线性基

基(basis)是线性代数中的一个概念,它是描述、刻画向量空间的基本工具。而在现行的 OI 题目中,通常在利用基在异或空间中的一些特殊性质来解决题目,而这一类题目所涉及的知识点被称作「线性基」。

首先来看一个问题:

给出N个数,要从中选出一个最大的子集,使得子集中的任意个元素异或值不为0.

这个和极大线性无关组有些类似。异或可以看出是模2域下的加法运算,如果把一个数转化为二进制,对应成一个由01构成的向量, 所有这些向量就构成了一个线性空间。

原问题就转化为求这个向量组的极大线性无关组,把这样一个极大线性无关组成为线性基。 可以用O(60 * 60 * n)的高斯消元来解决。

但是还有更加快速的构造线性基的方法: 复杂度是O(60 * n)

首先来证明一个性质:

取向量组中的两个向量a,b,把a,b中的某一个替换成a xor b, 替换前后向量组中的向量的线性组合得到的空间相同。 通俗的说就是 替换前后 能异或出来的值一样。

证明:

不妨把b替换成了c=a xor b.

对于一个值x,如果得到它 不需要用到b 那么替换没有影响,照样能得到x。

如果需要b b可以通过b=c xor a来得到, 因此替换后照样能组合出x。

也就是说 旧的向量组能组合出来的数 新的向量组也能组合出来。

反过来,对于新的向量组,如果把c替换成 c xor a 就得到了旧的向量组,根据上面的证明

新的向量组能组合出来的数 旧的向量组也能组合出来。

因此替换前后能组合出来的数一样。

所以如果把向量组里的向量互相异或, 极大线性无关向量组的大小是不变的。

线性基数组中有个性质,就是原数组中的任何数都可以通过线性基数组中的数异或和得到。

void solve(long long x){
	for(long long i=55;i>=0;i--){
		if(!(x>>i))continue;
		if(!p[i]){
			p[i]=x;
			break;
		}
		x^=p[i];
	}
}

例题

P3812 【模板】线性基

#include<bits/stdc++.h>
using namespace std;
const int N=100;
long long n,a,p[N];
long long ans;
void solve(long long x){
	for(long long i=55;i>=0;i--){
		if(!(x>>i))continue;
		if(!p[i]){
			p[i]=x;
			break;
		}
		x^=p[i];
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a;
		solve(a);
	}
	for(int i=55;i>=0;i--){
		if((ans^p[i])>ans){
			ans=ans^p[i];
		}
	}
	cout<<ans<<endl;
	return 0;
}

P4151 [WC2011] 最大XOR和路径

将每个环的异或值求出来,再找一条主路径,然后求这条主路径异或一些环的最大值即可

异或的最大值用线性基就可以了,但主路径怎么选呢?

仔细一想就会知道,其实主路径选哪条不影响最终结果,因为如果从起点到终点的路径有多条,那么这些路径之间一定构成了一些环,无论主路径怎么选,最终异或几个环总能得到所有的路径

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
const int N=5e4+10;
const int M=1e5+10;
int sum[N],p[N];
int idx,h[M*2],ne[M*2],to[M*2];
int w[M*2],vis[N];
void add(int x,int y,int z){
	to[++idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx;
}
void insert(int x){
	for(int i=63;i>=0;i--){
		if((x>>i)&1){
			if(!p[i]){
				p[i]=x;
				break;
			}
			else {
				x^=p[i];
				
			}
		}
	}
}
void dfs(int x,int s){
	sum[x]=s;
	vis[x]=1;
	for(int i=h[x];i;i=ne[i]){
		int y=to[i];
		if(!vis[y])dfs(y,sum[x]^w[i]);
		else insert(sum[x]^sum[y]^w[i]);
	}
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1,0); 
	int ans=sum[n];
	for(int i=63;i>=0;i--){
		ans=max(ans,ans^p[i]);
	}
	cout<<ans<<endl;
	return 0;
}

P4301 [CQOI2013] 新Nim游戏

那么,本题中,先手要胜利,则必须让后手无论拿掉哪几堆都弄不出异或和为0的情况。显然,这要用到线性基。

在插入线性基时,如果没成功插入,即最后x=0,那么意味着会出现异或和为0的情况,这时就要把x取走。显然,聪明的先手一定必胜,所以这题输出-1是不可能得分的了,我们只用考虑如何让取走的数量最小。

这时我们就要用到贪心思想:从大到小来试,能插入就插入,不能则取走。

那为啥这样结果最小呢?

粗略的证明一下,因为异或是不进位加法,那么,如果不是从大到小,假设ab…=x(a≤b≤...≤x),由于中途没有进位,因此,ab…≤a+b+...,所以显然取走x比取走那几堆更优。

如果并不是a≤b≤...≤x呢?排个序呗。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=110;
int a[N];
int n;
int p[N];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	int ans=0;
	for(int i=n;i>=1;i--){
		int x=a[i];
		for(int j=32;j>=0;j--){
			if((x>>j)&1){
				if(!p[j]){
					p[j]=x;
					break;
				}
				else{
					x^=p[j];
				}
			}
			
		}
		if(!x)ans+=a[i];
	}
	if(ans)cout<<ans<<endl;
	else cout<<-1<<endl;
	return 0;
}

posted on 2024-11-26 20:09  zsfzmxl  阅读(21)  评论(0编辑  收藏  举报

导航