笔记 线性基

定义

在线性代数中,基又称为基地,是刻画向量的工具。对于基底的元素我们称为基向量,向量空间的任意一个元素都可以唯一表示成为基向量的线性组合。同样,线性基也是一种基,它是一种特殊的基,一般用来求异或问题

我不知道你看懂没有,反正我是没看太懂,只需要知道线性基处理异或问题时十分方便就好。

性质

  • 将线性基中的一些元素异或一定可以得到完整的原数列
  • 原数列中异或的值域与线性基异或的值域相同
  • 线性基中没有异或和为0的子集
  • 线性基中选取元素的每种方案异或得到的数一定不相同
  • 对于一个数列来说,线性基中元素的个数是唯一的,并且是满足以上性质的最小集合

简单证明一下(为了防止有理解问题,用xor表示异或运算):
对于性质一,构建线性基时可以维护,下边再提。
对于性质二,可以由性质一推得。
对于性质三,如果有\(x_1 \ xor \ x_2 ………… xor\ x_n\ =\ 0\),那么一定有\(x_1\ xor\ x_2 ……\ xor \ x_n\ =\ x_i\),就是说\(x_i\)可以由已经有的元素异或得到,根据性质一可以知道,原数列的每一个数都能由线性基中的数异或得到,换言之,\(x_i\)没有必要加入线性基,所以性质三得证。
对于性质四,和性质三的推导大致相同,懒得写了。
对于性质五,综上可得。

基本操作

  1. 插入
    从高到低依次考虑该数字二进制的每一位,若该位是1,那么找到该位对应的线性基,如果是空的,直接替换掉并且退出插入,如果不是空的,那么用该数字异或线性基上的数,然后继续插入,直到找到一个空的线性基或者该数被异或为0,若找到一个空的线性基,则说明插入成功,若被异或成0,说明插入失败,这时可以知道该数字能被线性基中的一个子集异或得到,没有必要插入线性基。
    要注意的是左移时考虑左移的是1还是1ll这里很容易挂。
bool Insert(ll w){
	for(int i=62;i>=0;i--){
		if(w&(1ll<<i)){
			if(!bas[i]){
				bas[i]=w;
				return 1;
			}
			w^=bas[i];
		}
	}
	return 0;
}

2.查询数列异或和最小值
由线性基的性质可以知道,线性基中的最小的位上的不为0的数即答案,注意判断0的情况。
3.查询数列异或和最大值
根据插入的函数,第i个线性基中的数的最高位上的1是第i位,而高位上放1肯定比放0优,所以尽量让每个高位上都是1。

for(int i=55;~i;i--)
		if((ans^p[i])>ans)
			ans^=p[i];

或者

for(int i=62;i>=0;i--)
		if(b[i]&&((ans&(1ll<<i))==0))
			ans^=b[i];

4.查询第k小值,暂时不会。

例题

1.模板
这个就不说了,挺模板的。
2.元素
题意是让我们从一个序列中选出它的一个子集,使得选出来的数异或和不能得到0,且得到的数权值和最大。
异或和不能得到0可以构建一个线性基维护
线性基有一个很好的性质,就是插入顺序对线性基没有影响,可以参照上述性质五,既然这样,加一个大的也是加,加一个小的也是加,那肯定要加入大的啊,所以排一遍序一次考虑能不能加入线性基即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int lqs=1e3+10;
struct Node{
	ll idx;
	int val;
	bool operator<(const Node&A)const{
		return val>A.val;
	}
}p[lqs];
ll bas[70];
bool Ins(ll w){
	for(int i=62;i>=0;i--){
		if(w&(1ll<<i)){
			if(!bas[i]){
				bas[i]=w;
				return 1;
			}
			w^=bas[i];
		}
	}
	return 0;
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld%d",&p[i].idx,&p[i].val);
	}
	sort(p+1,p+n+1);
	int ans=0;
	for(int i=1;i<=n;i++){
		if(Ins(p[i].idx))
			ans+=p[i].val;
	}
	printf("%d\n",ans);
}

3.彩灯
这个会发现就是问这个序列的子集异或能得到多少不同的数,然后这几个字符串长度都不超过50,可以转换成十进制数。
将这几个十位数都丢到线性基里边然后线性基的集合个数就是答案,注意一个灯都不开也是一种情况,依据是上述性质一。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int lqs=60;
ll p[lqs],col[lqs];
char s[lqs];
bool Ins(ll w){
	for(int i=59;i>=0;i--){
		if(w&(1ll<<i)){
			if(!p[i]){
				p[i]=w;
				return 1;
			}w^=p[i];
		}
	}
	return 0;
}
int pow(int cnt,int w,int mod){
	int ans=1;
	while(cnt){
		if(cnt&1)ans=1ll*ans*w%mod;
		w=1ll*w*w%mod;
		cnt>>=1;
	}
	return ans;
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%s",s);
		for(int j=0;j<n;j++)
			if(s[j]=='O')col[i]|=1ll<<j;
	}
	int ans=0;
	for(int i=1;i<=m;i++)
		if(Ins(col[i]))ans++;
	printf("%d\n",pow(ans,2,2008));
}
posted @ 2020-06-28 16:50  An_Fly  阅读(512)  评论(4编辑  收藏  举报