线性代数

高斯消元

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

线性方程

形如

\[\begin{cases}a_{1,1}x_1+a_{1,2}x_2+...+a_{1,n}x_n=b_1\\a_{2,1}x_1+a_{2,2}x_2+...+a_{2,n}x_n=b_2\\...\\a_{n,1}x_1+a_{n,2}x_2+...+a_{n,n}x_n=b_n\\\end{cases} \]

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

系数矩阵

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

\[\begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n}\\ a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\ \vdots & \vdots & \ddots & \vdots\\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{bmatrix} \]

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

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

增广矩阵

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

如下。

\[\begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n}&b_1\\ a_{2,1} & a_{2,2} & \cdots & a_{2,n}&b_2\\ \vdots & \vdots & \ddots & \vdots&\vdots\\ a_{n,1} & a_{n,2} & \cdots & a_{n,n}&b_n \end{bmatrix} \]

行阶梯矩阵

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

如下。

\[\begin{bmatrix} c_{1,1} & c_{1,2} & c_{1,3} & \cdots & c_{1,n}&d_1\\ 0 & c_{2,2} & c_{2,3} & \cdots & c_{2,n}&d_2\\ 0&0&c_{3,3}&\cdots &c_{3,n} & d_3\\ \vdots & \vdots &\vdots& \ddots & \vdots&\vdots\\ 0 & 0 & 0&\cdots & c_{n,n}&d_n \end{bmatrix} \]

方程组解的判定

矩阵的行秩(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;
}
posted @ 2022-11-05 21:14  Code_AC  阅读(170)  评论(0编辑  收藏  举报