线性代数
高斯消元
高斯消元适用于求解线性方程。
线性方程
形如
的方程,我们称之为线性方程。
系数矩阵
我们可以将它们的系数提出来组成一个矩阵,我们称之为系数矩阵。如下。
矩阵的行变换(列变换)
1.将矩阵的第 \(i\) 行和第 \(j\) 行元素互换;
2.将矩阵的第 \(i\) 行元素分别乘 \(val\);
3.将矩阵的第 \(i\) 行元素分别乘 \(val\) 再加到第 \(j\) 行的对应位置。
由于矩阵可以倒置,行变换与列变换等价。
增广矩阵
定义:系数矩阵右侧加一列,这一列为方程等式右边的数值。
如下。
行阶梯矩阵
定义:非零行的第一个非零元素的列下标随着行数增加而严格递增,全零元素的行在末尾。
如下。
方程组解的判定
矩阵的行秩(rank):行阶梯矩阵非零行的数量 \(r\);
1.有唯一解:增广矩阵行秩 \(=\) 未知数数量;
2.有无数多组解:增广矩阵的行秩 \(<\) 未知数的数量;
3.无解:系数矩阵的行秩 \(\not=\) 增广矩阵的行秩。
复杂度 \(O(n^3)\)
例一 P3389【模板】高斯消元法
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int n;
double a[MAXN][MAXN];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++) scanf("%lf",&a[i][j]);
for(int i=1;i<=n;i++)
{
int maxn=i;
for(int j=i+1;j<=n;j++)
if(abs(a[j][i])>abs(a[maxn][i])) maxn=j;
for(int j=1;j<=n+1;j++)
swap(a[i][j],a[maxn][j]);
if(fabs(a[i][i])<0.001) {printf("No Solution\n");return 0;}
for(int j=1;j<=n;j++)
{
if(i==j) continue;
double rate=a[j][i]*1.0/a[i][i];
for(int k=1;k<=n+1;k++) a[j][k]-=a[i][k]*rate;
}
}
for(int i=1;i<=n;i++)
{
double ans=a[i][n+1]*1.0/a[i][i];
printf("%.2lf\n",ans);
}
return 0;
}
例二 P2455 [SDOI2006]线性方程组
真正意义上的高斯消元模板题,将三种情况完全分开了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
const double eps=1e-6;
int n;
double a[MAXN][MAXN];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++) scanf("%lf",&a[i][j]);
for(int i=1;i<=n;i++)
{
int maxn=i;
for(int j=i+1;j<=n;j++)
if(abs(a[j][i])>abs(a[maxn][i])) maxn=j;
for(int j=1;j<=n+1;j++)
swap(a[i][j],a[maxn][j]);
if(fabs(a[i][i])<eps) continue;
for(int j=1;j<=n;j++)
{
if(i==j) continue;
double rate=a[j][i]*1.0/a[i][i];
for(int k=1;k<=n+1;k++) a[j][k]-=a[i][k]*rate;
}
}
for(int i=1;i<=n;i++)
{
double sum=0;
for(int j=1;j<=n;j++)
sum+=a[i][j];
if(sum==0 && fabs(a[i][n+1])>=eps) {printf("-1\n");return 0;}
}
for(int i=1;i<=n;i++)
if(fabs(a[i][i])<=eps && fabs(a[i][n+1])<=eps) {printf("0\n");return 0;}
for(int i=1;i<=n;i++)
{
double ans=a[i][n+1]*1.0/a[i][i];
printf("x%d=%.2lf\n",i,ans);
}
return 0;
}
例三 P3164 [CQOI2014]和谐矩阵
只是把高斯消元用在了异或上。
那么我们就将系数全设为 \(1\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=55;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1;ch=getchar(); }
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
int dx[]={0,0,0,1,-1},dy[]={0,-1,1,0,0};
int n,m,id[MAXN][MAXN],ans[MAXN*MAXN];
bitset<MAXN*MAXN>a[MAXN*MAXN];
inline void solve()
{
for(int i=1;i<=n*m;i++)
{
for(int j=i;j<=n*m;j++)
if(a[j][i]>0) {swap(a[i],a[j]);break;}
if(!a[i][i]) ans[i]=1;
for(int j=i+1;j<=n*m;j++)
if(a[j][i]) a[j]^=a[i];
}
for(int i=n*m;i>=1;i--)
for(int j=i+1;j<=n*m;j++)
ans[i]^=(ans[j]*a[i][j]);
return;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
id[i][j]=(i-1)*m+j;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<=4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(x<1 || y<1 || x>n || y>m) continue;
a[id[i][j]][id[x][y]]=1;
}
solve();
for(int i=1;i<=n*m;i++)
{
printf("%d ",ans[i]);
if(!(i%m)) puts("");
}
return 0;
}
例四 P4035 [JSOI2008]球形空间产生器
其实也是板子题,就是用每相邻两个凑一个方程,因为刚好给的 \(n+1\) 个点。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
double a[MAXN][MAXN];
double ans[MAXN];
double eps=1e-7;
int n;
int main()
{
scanf("%d",&n);n++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<n;j++)
{
scanf("%lf",&a[i][j]);
a[i][n+1]-=a[i][j]*a[i][j];a[i][j]*=2.0;
}
a[i][n]=1;
}
for(int i=1;i<=n;i++)
{
int maxn=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[maxn][i])<fabs(a[j][i])) maxn=j;
if(fabs(a[maxn][i])<eps) return 0;
if(i!=maxn) swap(a[i],a[maxn]);
double rate=a[i][i];
for(int j=i;j<=n+1;j++) a[i][j]/=rate;
for(int j=i+1;j<=n;j++)
{
rate=a[j][i];
for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*rate;
}
}
ans[n]=a[n][n+1];
for(int i=n-1;i>=1;i--)
{
ans[i]=a[i][n+1];
for(int j=i+1;j<=n;j++) ans[i]-=(a[i][j]*ans[j]);
}
for(int i=1;i<n;i++) printf("%.3lf ",-ans[i]);
return 0;
}
例五 P2962 [USACO09NOV]Lights G
这是一道解异或方程的题。。。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
int n,m;
bool a[MAXN][MAXN];
inline bool solve()
{
bool flag=true;
for(int i=1;i<=n;i++)
{
int maxn=i;
while(maxn<=n && a[maxn][i]==0) maxn++;
if(maxn>n) {flag=false;continue;}
swap(a[i],a[maxn]);
for(int j=1;j<=n;j++)
{
if(i==j || a[j][i]==0) continue;
for(int k=i;k<=n+1;k++) a[j][k]^=a[i][k];
}
}
return flag;
}
bool vis[MAXN];
int ans=1e9;
inline void dfs(int x,int num)
{
if(num>=ans) return;
if(x==0) {ans=num;return;}
if(a[x][x])
{
bool val=a[x][n+1];
for(int i=x+1;i<=n;i++)
if(a[x][i]) val^=vis[i];
dfs(x-1,num+val);
}
else
{
dfs(x-1,num);vis[x]=1;
dfs(x-1,num+1);vis[x]=0;
}
return;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
a[i][i]=a[i][n+1]=1;
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
a[x][y]=a[y][x]=1;
}
if(solve()==true)
{
ans=0;
for(int i=1;i<=n;i++)
ans+=a[i][n+1];
printf("%d\n",ans);
}
else
{
dfs(n,0);
printf("%d\n",ans);
}
return 0;
}
线性基
向量
在信息学中可以理解为一个数列。
eg: \(a=\{1,2,3\}\) 是一个三维向量。
向量的加法
eg: \(a=\{1,2,3\},b=\{4,5,6\},a+b=\{1,2,3,4,5,6\}\)
向量的数乘
eg \(a=\{1,2,3\},a\times 10=\{10,20,30\}\)
一些概念
1.线性空间
定义:设 \(n\) 个向量通过线性组合能够表示出的所有向量的集合。
2.线性相关
定义:设一组向量 \(a_1,a_2,...,a_n\),若其中存在一个向量 \(a_i\) 可以由其余向量线性表示出来,则称该组向量线性相关。
3.线性无关
定义:相对的,设一组向量 \(a_1,a_2,...,a_n\),若其中任意一个向量 \(a_i\) 都不能由其余向量线性表示出来,则称该组向量线性无关。
4.线性基底
定义:设一组向量存在一个子集,该子集的向量是线性无关的,且该子集和原向量能表示出相同的线性空间,则称该子集为该组向量的一组线性基。
求法
将一个向量视为矩阵的一行,则 \(n\) 个 \(m\) 维的向量可以表示成一个 \(n\) 行 \(m\) 列的矩阵。
利用矩阵行变换,化简为行阶梯矩阵,则非零行向量就是一组线性基。
时间复杂度较高,为 \(O(n^3)\)
例一 P3265 [JLOI2015]装备购买
1.\(n\) 个 \(m\) 维的向量;
2.找出其中最便宜的一组线性基。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-5;
const int MAXN=505;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();}
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
struct node
{
double a[MAXN];
int val;
inline bool operator<(const node x)const
{
return val<x.val;
}
}x[MAXN];
int y[MAXN],n,m,ans,cnt;
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lf",&x[i].a[j]);
for(int i=1;i<=n;i++) x[i].val=read();
sort(x+1,x+1+n);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(abs(x[i].a[j])<=eps)continue;
if(!y[j])
{
y[j]=i;cnt++;
ans+=x[i].val;
break;
}
else
{
double rat=x[i].a[j]/x[y[j]].a[j];
for(int k=j;k<=m;k++)
x[i].a[k]-=rat*x[y[j]].a[k];
}
}
printf("%d %d",cnt,ans);
return 0;
}
例二 P3812【模板】线性基
模板,我们常用构造法。采用二进制拆位的思路。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=65;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1;ch=getchar(); }
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
int vis[MAXN];//基底的集合,p[i]存储在二进制数位i上贡献1的元素。
int n,ans;
inline void check(int x)//将x尝试插入基底的集合
{
for(int i=63;i>=0;i--)
{
if(x>>i) if(vis[i]==0) {vis[i]=x;break;}
else x=x^vis[i];
}
return;
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
int x=read();
check(x);
}
for(int i=63;i>=0;i--) ans=max(ans,ans^vis[i]);
printf("%lld\n",ans);
return 0;
}