线性基学习笔记
对于一个 维向量组,每一个向量表示为形如
如果存在一个向量可以用其他向量表示出来,称为线性相关
否则,称为线性无关
所有向量组可以形成的向量集合称为线性空间
求出向量组的一个线性无关的子集,其可以组成的线性空间不变,称为线性空间的一组基
对于一个向量组,对于其基的求解可以用高斯消元来实现
证明高斯消元的操作对线性空间的大小没有影响:
- 交换两行:显然没有影响
- 加上另一行的数倍:相当于加上另一个向量,那么这个向量本身可以用新形成的向量表示出来,也没有影响
于是通过高斯消元求出最大的基底
虽然在 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;
}
和上面一样的套路,先排序,线性基用作判断能否加入
为了让剩余火柴的子集不为零,构建线性基,排序后能插入则插入,否则拿走
用到线性基的结论:若基中值有 个,那么可以拼凑出的个数为
这道题的不同之处在于不用去重,那么要用到另一个结论,每一个能拼凑的数的拼凑方案数是 ,即随便一个基外的子集都可以添加进来
这就要用到线性基维护图上问题的新科技了
可以发现由于路径的可重,那么最终的路径的一定是一条简单路径外加许多环(因为环相当于是一去一回,而重叠部分相互抵消)
于是把所有还放进线性基即可,由于环的个数很多,但是不同环之间可以由异或得出,所以只放返祖边形成的环即可
另外简单路径是可以随意选的,因为和其他路径可以通过异或环得出
CF724G Xor-matic Number of the Graph
好的,现在是前面两天道题的结合版,首先一样的把所有环放进线性基里。
每一位的贡献分开考虑
若有环这一位为 ,那么任意两点异或这个环便可以加上这位贡献的
方案数为
若没有环这一位为 ,那么只有路径这一位为 才能产生贡献
方案数为 ,其中 表示距离这一位为 的点的个数
注意图可能不联通
接下来就是线性基的合并了,由于线性基是 位的,那么直接暴力合并 即可
操作用线段树都能维护,直接上即可
发现这次不能维护了,因为修改变成了区间修改
考虑将区间修改变成单点修改,那么差分即可
但是这样就需要发现差分数组与原数组线性基的关系了
发现原数组展开后差分数组 的部分是重叠的,那么可以发现原数组的线性基于 加上 的线性基是等价的
那么用线段树维护差分数组的线性基,用树状数组动态维护原数组的值即可
发现直接合并的复杂度实在太暴躁了,在有些题中不足以通过,那么需要再加入一些小
考虑离线回答
可以按照右端点排序,只要线性基中的数在左端点右侧即可使用
那么每次有冲突时可以贪心地选择位置靠右的
一样的套路,维护每个点到根的线性基,深度越深越好,查询时深度大于 即可使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效