不相信自己的人,连努力的价值都没有。|

Code_AC

园龄:3年粉丝:5关注:3

线性代数

高斯消元

高斯消元适用于求解线性方程。

线性方程

形如

{a1,1x1+a1,2x2+...+a1,nxn=b1a2,1x1+a2,2x2+...+a2,nxn=b2...an,1x1+an,2x2+...+an,nxn=bn

的方程,我们称之为线性方程。

系数矩阵

我们可以将它们的系数提出来组成一个矩阵,我们称之为系数矩阵。如下。

[a1,1a1,2a1,na2,1a2,2a2,nan,1an,2an,n]

矩阵的行变换(列变换)
1.将矩阵的第 i 行和第 j 行元素互换;
2.将矩阵的第 i 行元素分别乘 val
3.将矩阵的第 i 行元素分别乘 val 再加到第 j 行的对应位置。

由于矩阵可以倒置,行变换与列变换等价。

增广矩阵

定义:系数矩阵右侧加一列,这一列为方程等式右边的数值。

如下。

[a1,1a1,2a1,nb1a2,1a2,2a2,nb2an,1an,2an,nbn]

行阶梯矩阵

定义:非零行的第一个非零元素的列下标随着行数增加而严格递增,全零元素的行在末尾。

如下。

[c1,1c1,2c1,3c1,nd10c2,2c2,3c2,nd200c3,3c3,nd3000cn,ndn]

方程组解的判定

矩阵的行秩(rank):行阶梯矩阵非零行的数量 r

1.有唯一解:增广矩阵行秩 = 未知数数量;
2.有无数多组解:增广矩阵的行秩 < 未知数的数量;
3.无解:系数矩阵的行秩 增广矩阵的行秩。

复杂度 O(n3)

例一 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×10={10,20,30}

一些概念

1.线性空间

定义:n 个向量通过线性组合能够表示出的所有向量的集合。

2.线性相关

定义:设一组向量 a1,a2,...,an,若其中存在一个向量 ai 可以由其余向量线性表示出来,则称该组向量线性相关。

3.线性无关

定义:相对的,设一组向量 a1,a2,...,an,若其中任意一个向量 ai 都不能由其余向量线性表示出来,则称该组向量线性无关。

4.线性基底

定义:设一组向量存在一个子集,该子集的向量是线性无关的,且该子集和原向量能表示出相同的线性空间,则称该子集为该组向量的一组线性基。

求法

将一个向量视为矩阵的一行,则 nm 维的向量可以表示成一个 nm 列的矩阵。
利用矩阵行变换,化简为行阶梯矩阵,则非零行向量就是一组线性基。

时间复杂度较高,为 O(n3)

例一 P3265 [JLOI2015]装备购买

1.nm 维的向量;
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;
}

本文作者:Code_AC

本文链接:https://www.cnblogs.com/code-ac/p/16861339.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Code_AC  阅读(172)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起