线性基的应用

1.异或运算的线性基

在一个异或空间(就是一堆数)中,几个数用异或运算可以表示出来所有的数,这几个数就是这个异或空间的线性基

线性基是满足条件的极大子集

一个异或空间的线性基可能有很多个,但是线性基的数目是一定的(因为线性基是满足条件的极大子集)

构造方法:对于一个要加入线性基的数,找到最高位的1,若记录的这一位是1的已经有了就异或上它继续寻找,若没有则记录这个数

	for(int i=1;i<=a;i++){
		ll o;
		scanf("%lld",&o);
		for(int j=60;j>=0;j--){
			if((o>>j)&1){
				if(!w[j]){
					w[j]=o;
					break;
				}
				else o^=w[j];
			}
		}
	}

为什么这样是对的呢?

假设现在有x1,第一个1在二进制的第i位,记录的数是x2,最后x1以x3的值记录下来了

在最后选的过程,若选x2,则相当于选择了x2,若选x3,相当于x1和x2都选择了(x3=x1^x2)

若选x2与x3,相当于只选了x1((x1^x2)^x2=x1)

异或运算中的线性基的几个应用:

1.查询一个数是否能被异或空间中的数异或出来

对于这个数,若二进制第i为为1,w[i]若为0返回false,否则异或上w[i]继续查找

bool query(ll x){
	for(int i=60;i>=0;i--){
		if(x&(1ll<<i)){
			if(!w[i]) return false;
			x^=w[i];
		}
	}
	return true;
}

2.异或最大值

因为线性基的异或可以表现为所有数的异或,所有只用线性基做异或运算就可以求出来所有数的异或最大值,从高到第选取二进制意义下每一位的值,若第i位记录的有数且异或上这个数后答案会变大(其实就是原来累计答案的这一位为0)就选择异或上这个数

for(int i=60;i>=0;i--){
		if((ans^w[i])>ans) ans^=w[i];
}

最小值只要找到最小一位记录的就行了(0要特判) 

3.异或第k大

将线性基的每一位做一些处理,让不同位异或起来答案一定变大(就是保证一下为1的位数都不相同)

然后考虑第k大,将k二进制分解,若一位为1,说明选择那一位代表的值,否则不选

ll w[100],d[100],gs;
	bool zero;
	void init(){
		memset(w,0,sizeof(w));
		memset(d,0,sizeof(d));
		gs=0;
	}
	void insert(ll x){
		for(int i=63;i>=0;i--){
			if(!(x>>i&1)) continue;
			if(!w[i]){
				w[i]=x;return ;
			}else x^=w[i];
		}
		zero=true;
	}
	void rebuild(){
		for(int i=63;i>=0;i--){
			for(int j=i-1;j>=0;j--){
				if((w[i]>>j)&1) w[i]^=w[j];
			}
		}
		for(int i=0;i<=63;i++){
			if(w[i]) d[gs++]=w[i];
		}
	}
	ll query(ll k){
		if(zero){
			if(k==1) return 0;
			else k--;
		}
		if(k>=(1ll<<gs)) return -1;//因为w只有gs个有效数字,所以总共能表示2^gs次方的数
		ll ans=0;
		for(int i=0;i<=gs;i++){
			if(k&(1ll<<i)) ans^=d[i];
		}
		return ans;

[TJOI2008]彩灯

将每个开关控制的灯看成一个二进制的数,故所有灯的状态都能由二进制数来表示。开关的控制可以看作是两个数的异或运算,故这道题的题意就简化为了:

有n个数,这n个数能表示出的数的数量是多少

因为线性基就可以表示出来异或空间的所有可能性,对于在线性基里面的数,每个都是可选可不选,不同选择所得到的答案也一定是不同的,所以总的答案就是2^n,n是能插入线性基的数

ll w[55],mod=2008;
	void insert(ll x){
		for(int i=52;i>=0;i--){
			if(x&(1ll<<i)){
				if(!w[i]){
					w[i]=x;break;
				}else x^=w[i];
			}
		}
	}
	ll query(){
		ll ans=1;
		for(int i=0;i<=52;i++){
			if(w[i]) ans=ans*2%mod; 
		}
		return ans;
	}

新nim博弈

nim游戏先手必胜:所有石子堆的个数异或和不为0
证明:若所有石子堆异或和不为0,那么可以算出他们的异或值x,可知至少有一个i使(x^a[i])<a[i] 
因为假设x是1的最高位为j,一定有一个a[i]是1最高位是j,异或后值一定比原数小

本题要求先取一堆石子,使得剩余的石子任意几堆的异或和都不为0。可知道剩余的石子堆数就是线性基中的数目,因为线性基的一个性质是互相进行运算的值不为0,且它是满足性质的最大子集,也就是能使剩下的石子堆数(要拿走的)最少 
故将石子堆按照数目排序,从大到小依次插入线性基,不能插入的价值和就是答案 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct xxj{
	ll w[55],ans;
	void insert(ll x){
		ll now=x;
		for(int i=52;i>=0;i--){
			if(!(now&(1ll<<i))) continue;
			if(!w[i]){
				w[i]=now;
				return ;	
			}
			now^=w[i];
 		}
 		ans+=x;
	}
};
xxj ty;
int a;
long long n[110];
int main(){
	scanf("%d",&a);
	for(int i=1;i<=a;i++){
		scanf("%lld",&n[i]);
	}
	sort(n+1,n+a+1);
	for(int i=a;i>=1;i--) ty.insert(n[i]);
	printf("%lld",ty.ans);
	return 0;
}

2.线性空间的线性基

线性空间:在各个向量中做加法和乘法运算封闭的一个集合 
 线性空间中的线性基:其中元素互相不能表出,但是经过加法和乘法可以表出线性空间的所有的元素(就是一个空间的维度)
 实数线性基的求法:高斯消元,消元后所有非0行的个数组成这个矩阵的线性基 (因为高斯消元后若一行都为0,说明这一行所代表的向量就可以被其他的表示出来,故这一行一定不在线性基内)

装备购买

将每一行的装备性质看作是一个向量,若这个装备性质能被其他装备表示出来,就不用买这个装备了,求花费的最少的钱

求出所有装备表示出来的线性空间的基,因为线性基有好多种但数量一定,所以想要它的价格最低,就将所有的装备先排序后插入线性基

#include<bits/stdc++.h>
using namespace std;
double eps=1e-2;
int n,m,gs;
double a[510][511],w[511],ans;
bool use[511];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%lf",&a[i][j]);
		}
	}
	for(int i=1;i<=n;i++) scanf("%lf",&w[i]);
	for(int i=1;i<=n;i++){
		int id;
		double wnow=10000000000.00;
		bool ok=false;
		for(int j=1;j<=n;j++){
			if((a[j][i]-eps>=0||a[j][i]+eps<=0)&&wnow>w[j]&&!use[j]){
				wnow=w[j],id=j,ok=true;
			}
		}
		if(!ok) continue;
		ans+=w[id],use[i]=true,gs++;
		swap(w[i],w[id]);
		for(int j=1;j<=m;j++) swap(a[i][j],a[id][j]);
		double h=a[i][i];
		for(int j=i;j<=m;j++) a[i][j]/=h;
		for(int j=1;j<=n;j++){
			if(!use[j]){
				double h=a[j][i];
				for(int k=1;k<=m;k++){
					a[j][k]-=a[i][k]*h;
				}
			}
		}
	}
	printf("%d %d",gs,(long long)ans);
	return 0;
} 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值