线性基(学习笔记)

看看别人的线性基学习笔记

每一次遇到异或和最大的题目,都懵得要死,然后大佬讲题的时候都是"线性基模板题".于是就浅学线性基,然后发现线性基是一个概念比实现难得多的东西.(良心建议:不要觉得线性基这个名字很高大上,就觉得线性基很难,虽然它的定理确实很难,但它的实现真的简单.就把它当做一个新名词就行)

因为博主数学不好,所以不会证明各种有关线性基的定理,所以也就不会把网上那些定理直接kuai过来了(没意义).我的线性基一般用于求异或和的题目.

线性基其实就是一个数的集合(我自己的理解),我们定义p[ ]是线性基,则p[i]表示的是 (表示成二进制数时)最高位的1在第i位上 的数.

因为下面第一道例题是裸线性基,所以模板直接放在那里了.

传送门:模板

题意:给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大.

一道普普通通的模板题,对于给出的n个数,我们构建出它们的线性基p[ ],然后枚举p数组中的数,如果ans异或它之后变大了,就更新ans.

int n;
long long ans,p[61];
void get_LB(long long x){
    for(int i=60;i>=0;i--)
		if(x&(1LL<<i)){//写成1<<i会爆掉
//x&(1LL<<i)=1表示把x看做二进制数后,第i位是1
//因为线性基的定义,所以我们要从最高位枚举到最低位
	    	if(!p[i]){
//如果线性基中没有 最高位上的1在第i位上的数
//就把当前这个数放入线性基,直接break掉
				p[i]=x;
				break;
	    	}
	    	x=x^p[i];
//如果线性基中已经存在 最高位上的1在第i位上的数
//就执行x=x^p[i],即把x当前最高位上的1变为0,继续循环
//因为只要x还没被异或到0,它就还可能产生贡献
		}
    return;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++)
		get_LB(read());
    for(int i=60;i>=0;i--)
		if((ans^p[i])>ans)ans=ans^p[i];
//贪心地从最高位枚举到最低位,保证异或和最大
    printf("%lld
",ans);
    return 0;
}

传送门:[BJWC2011]元素

题意:有n种矿石,每种矿石有一个元素序号和魔力值,从中选出一些矿石使得它们的魔力值最大(选出的矿石的元素序号的异或和不能为0)

分析:题目对于选出的矿石有两个条件,一是矿石的元素序号的异或和不能为0,这个可以用线性基来维护.二是需要得到的权值最大,那么直接对于每件物品按权值排序,按权值从大到小插入到线性基中就可以保证得到的线性基中的元素是权值之和最大的.

int n;
long long ans,p[62];
struct node{
    long long num,val;
}a[1005];
bool cmp(node x,node y){return x.val>y.val;}
bool get_LB(long long x){
    for(int i=62;i>=0;i--){
		if(x&(1LL<<i)){
	    	if(!p[i]){
				p[i]=x;
				break;
	    	}
	    	x=x^p[i];
		}
    }
    return x>0;
}
//把元素序号插入线性基的函数 设为bool型
//构建线性基的同时,还可以判断这个数是否插入了线性基
//x>0表示x成功插入了线性基,即这种矿石我们可以选择
int main(){
    n=read();
    for(int i=1;i<=n;i++){
		a[i].num=read();
		a[i].val=read();
    }
    sort(a+1,a+n+1,cmp);//按权值从大到小排序
    for(int i=1;i<=n;i++)
		if(get_LB(a[i].num))ans+=a[i].val;
//如果这种矿石可以选择,就加上它的权值
    printf("%lld
",ans);
    return 0;
}

题意:给定n个数,(a_1,a_2...a_n),每次你可以选两个数(a_i,a_j),把(a_i)变为(a_i)^(a_j),你可以选无限次,但要使最后得到n个数的和最大.

分析:本题中n<=1000,我们可以构建一个(60*n^2)的算法:从左往右依次求出XOR最大值.即:首先序列为a1,a2,a3..an,求出a1 XOR 其他数所得的最大值a1',序列变为 a1',a2,a3...an,再求出 a2 XOR a1',a3,a4...an 的最大值a2',序列又变为 a1',a2',a3...an,直到求出 a1',a2',a3'...an'即为目标的一个可行序列.

因为这个做法接近于暴力,所以比较好想,也比较好写.

下面这篇博客分别还介绍了一种更好的做法.

方法2

int n;
long long ans,a[1005],p[63];
void get_LB(long long x){
    memset(p,0,sizeof(p));//记得初始化
    for(int i=1;i<=n;i++){
		long long now=a[i];
		if(now==x)continue;
		for(int j=60;j>=0;j--)
	    	if(now&(1LL<<j)){
				if(!p[j]){
		    		p[j]=now;
		    		break;
				}
				now=now^p[j];
	    	}
    }
}
int main(){
    n=read();
    for(int i=1;i<=n;i++)
		a[i]=read();
    for(int i=1;i<=n;i++){
		long long now=a[i];
		get_LB(now);
		for(int j=60;j>=0;j--){
	    	if(now&(1LL<<j))continue;
//这句的意思是,从最高位枚举到最低位,如果是1则跳过
//为什么跟构建线性基的函数恰恰相反呢?
//因为我们要保证now最大,肯定不能让它的1被消掉啊
//我们只能贪心地让它高位上的0尽可能变为1
	    	now=now^p[j];
		}
		a[i]=now;//now就是a[i]'
		ans+=now;
    }
    printf("%lld
",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/10387626.html