线性基学习笔记

对于一个 \(m\) 维向量组,每一个向量表示为形如 \((x_1,x_2,...,x_m)\)
如果存在一个向量可以用其他向量表示出来,称为线性相关
否则,称为线性无关
所有向量组可以形成的向量集合称为线性空间
求出向量组的一个线性无关的子集,其可以组成的线性空间不变,称为线性空间的一组基

对于一个向量组,对于其基的求解可以用高斯消元来实现
证明高斯消元的操作对线性空间的大小没有影响:

  • 交换两行:显然没有影响
  • 加上另一行的数倍:相当于加上另一个向量,那么这个向量本身可以用新形成的向量表示出来,也没有影响

于是通过高斯消元求出最大的基底

虽然在 OI 中实数的基底很不常见,但是这是基底的本质

比如这道题可以作为模板:P3265 [JLOI2015]装备购买

题目中线性无关的限制太明显了,提示需要构建基底
这道题里由于有了价格的限制,可以先排个序,再把高斯消元的过程动态进行
具体来说是这样的:从大到小枚举每一位,如果某一维还没有基,那么可以直接把这个向量作为那一维的基
否则,将这一维和这一维的基加减抵消成零

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
int n,m,ans,ans1,b[maxn];
double eps=1e-5;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct Node{
	double a[maxn];
	int val;
}p[maxn];
bool operator < (Node a,Node b){
	return a.val<b.val;
}
double ffabs(double x){
	return x<0?-x:x;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>p[i].a[j];
	for(int i=1;i<=n;i++)p[i].val=read();
	sort(p+1,p+n+1);
	for(int i=1;i<=n;i++){
		for(int j=m;j>=1;j--){
			if(ffabs(p[i].a[j])<eps)continue;
			if(!b[j]){
				b[j]=i;ans+=p[i].val;ans1++;
				break;
			}
			double chu=p[i].a[j]/p[b[j]].a[j];
			for(int k=j;k>=1;k--){
				p[i].a[k]-=chu*p[b[j]].a[k];
			}
		}
	}
	cout<<ans1<<" "<<ans;
	return 0;
}

在 OI 中线性基几乎特指在异或中的应用
模板题为例,要求最大异或子集
可以模仿构建基底的过程,从高到低确定每一位,根据二进制的性质,这样一定是最优的
根据基底的本质来理解,如果构建出线性基,相当于可以异或出原来的数能异或得到的所有数
那么答案直接在线性基上贪心选取每一位即可

  • 最后注意一点:大于的优先级是高于异或的哦~
代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,x,a[100],ans;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++){
		x=read();
		for(int j=50;j>=0;j--){
			if((x>>j)&1){
				if(a[j])x^=a[j];
				else{
					a[j]=x;
					break;
				}
			}
		}
	}
	for(int i=50;i>=0;i--)if(!((ans>>i)&1))ans^=a[i];
	cout<<ans;
	return 0;
}

P4570 [BJWC2011]元素

和上面一样的套路,先排序,线性基用作判断能否加入


P4301 [CQOI2013] 新Nim游戏

为了让剩余火柴的子集不为零,构建线性基,排序后能插入则插入,否则拿走


P3857 [TJOI2008]彩灯

用到线性基的结论:若基中值有 \(cnt\) 个,那么可以拼凑出的个数为 \(2^{cnt}\)


P4869 albus就是要第一个出场

这道题的不同之处在于不用去重,那么要用到另一个结论,每一个能拼凑的数的拼凑方案数是 \(2^{n-cnt}\),即随便一个基外的子集都可以添加进来


P4151 [WC2011]最大XOR和路径

这就要用到线性基维护图上问题的新科技了

可以发现由于路径的可重,那么最终的路径的一定是一条简单路径外加许多环(因为环相当于是一去一回,而重叠部分相互抵消)

于是把所有还放进线性基即可,由于环的个数很多,但是不同环之间可以由异或得出,所以只放返祖边形成的环即可

另外简单路径是可以随意选的,因为和其他路径可以通过异或环得出


CF724G Xor-matic Number of the Graph

好的,现在是前面两天道题的结合版,首先一样的把所有环放进线性基里。

每一位的贡献分开考虑
若有环这一位为 \(1\),那么任意两点异或这个环便可以加上这位贡献的 \(1\)
方案数为 \(2^{|S|-1}\binom{n}{2}\)

若没有环这一位为 \(1\),那么只有路径这一位为 \(1\) 才能产生贡献
方案数为 \(2^{|S|}cnt(n-cnt)\),其中 \(cnt\) 表示距离这一位为 \(1\) 的点的个数

注意图可能不联通


接下来就是线性基的合并了,由于线性基是 \(log\) 位的,那么直接暴力合并 \(log^2\) 即可


P4839 P哥的桶

操作用线段树都能维护,直接上即可


P5607 [Ynoi2013] 无力回天 NOI2017

发现这次不能维护了,因为修改变成了区间修改
考虑将区间修改变成单点修改,那么差分即可
但是这样就需要发现差分数组与原数组线性基的关系了
发现原数组展开后差分数组 \(b_{[1,l]}\) 的部分是重叠的,那么可以发现原数组的线性基于 \(a_l\) 加上 \(b_{[l+1,r]}\) 的线性基是等价的

那么用线段树维护差分数组的线性基,用树状数组动态维护原数组的值即可


发现直接合并的复杂度实在太暴躁了,在有些题中不足以通过,那么需要再加入一些小 \(trick\)


CF1100F Ivan and Burgers

考虑离线回答
可以按照右端点排序,只要线性基中的数在左端点右侧即可使用
那么每次有冲突时可以贪心地选择位置靠右的


P3292 [SCOI2016]幸运数字

一样的套路,维护每个点到根的线性基,深度越深越好,查询时深度大于 \(lca\) 即可使用

posted @ 2021-11-16 21:46  y_cx  阅读(405)  评论(0编辑  收藏  举报