模板 - 高斯约旦消元法

Posted yinku

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板 - 高斯约旦消元法相关的知识,希望对你有一定的参考价值。

真丶long double高斯约旦消元法

eps需要取得大一些,以免增加了矩阵的秩。
long double可能会慢一些但是无所谓,被卡精度太恶心了。

需要知道一些线代的知识(线代67说你呢!),比如秩、极大线性无关组(线性基)之类的。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

namespace Gauss_Jordan_Elimination {
    const int MAXN=505;
    const int MAXM=505;
    long double a[MAXN][MAXM+1];
    double ans[MAXN];

    const long double eps=1e-6;

    //返回增广矩阵的秩,-1表示无解
    int gauss_jordan(int n,int m) {
        int r=0;
        for(int i=1; i<=m; i++) {
            //当前是第i列
            int mx=-1;
            //从当前秩的下一行开始往下数
            for(int j=r+1; j<=n; j++)
                if(mx==-1||fabs(a[j][i])>fabs(a[mx][i])){
                    mx=j;
                }
            if(mx==-1) {
                //该列全0,被跳过
                continue;
            }
            r++;
            //增加一个线性基,当前秩增加
            if(mx!=r) {
                //需要交换
                for(int j=1; j<=m+1; j++)
                    //m+1表示增广矩阵
                    swap(a[r][j],a[mx][j]);
                //交换行
            }

            for(int j=1; j<=n; j++) {
                //枚举每一行
                if(j!=r&&fabs(a[j][i])>eps) {
                    //消去除了r以外的其他行
                    long double tmp=a[j][i]/a[r][i];
                    //该行系数是当前列的tmp倍
                    for(int k=i; k<=m+1; k++) {
                        //把当前列对应行位置扩大tmp倍,然后用每个元素减去,由高斯约当的过程,左边的绝对是0
                        a[j][k]-=a[r][k]*tmp;
                    }
                }
            }
        }

        /*for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                printf(" %.2f ",a[i][j]);
            }
            printf("\n");
        }*/

        /*当不需要关心方程右边时,删去以下部分*/
        int j=1;
        for(int i=1; i<=r; i++){
            //找第一个非0值,也就是阶梯型矩阵的第一个值,因为是高斯约当消元所以其他位置都是0
            while(fabs(a[i][j])<eps)
                j++;

            if(fabs(a[i][m+1])<eps){
                //在秩里面,居然有右侧为0的?
                return -1;
            }
            //消去x的系数,系数化为1
            ans[i]=(double)(a[i][m+1]/a[i][j]);

            j++;
            if(j>m){
                break;
            }
        }

        return r;
    }
}

using namespace Gauss_Jordan_Elimination;

int n,m;

int main() {
    scanf("%d",&n);
    m=n;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m+1; j++) {
            double tmp;
            scanf("%lf",&tmp);
            a[i][j]=tmp;
        }
    }

    int r=gauss_jordan(n,m);

    if(r!=n){
        puts("No Solution");
    }
    else{
        for(int i=1;i<=r;i++)
            printf("%.2f\n",ans[i]);
    }
}

https://www.luogu.org/problemnew/show/P3265

#include<bits/stdc++.h>
#define ll long long
using namespace std;

/**
 *某个线性方程组的矩阵的秩是确定的
 *用高斯约旦消元可以求出矩阵的秩
 *明显可以贪心,每次选择最便宜的非零向量?
 *当矩阵为零矩阵时,可以买最便宜的零向量
 */

int price[505];

int r=0;

namespace Gauss_Jordan_Elimination {
    const int MAXN=505;
    long double a[MAXN][MAXN];
    long double ans[MAXN];

    const long double eps=1e-6;

    bool gauss_jordan(int n,int m) {
        r=0;
        for(int i=1; i<=m; i++) {
            //当前是第i列
            int mx=-1;
            //从当前秩的下一行开始往下数
            for(int j=r+1; j<=n; j++)
                if(fabs(a[j][i])>eps){
                    if(mx==-1||price[j]<price[mx]){
                        mx=j;
                    }
                }
            if(mx==-1) {
                //该列全0,被跳过
                continue;
            }
            r++;
            //增加一个线性基,当前秩增加
            if(mx!=r) {
                //最小花费的行需要交换
                for(int j=1; j<=m; j++)
                    swap(a[r][j],a[mx][j]);
                //交换行
                swap(price[r],price[mx]);
                //这两个装备交换了位置,价格当然也变了
            }

            for(int j=1; j<=n; j++) {
                //枚举每一行
                if(j!=r&&fabs(a[j][i])>eps) {
                    //消去除了r以外的其他行
                    long double tmp=a[j][i]/a[r][i];
                    //该行系数是当前列的tmp倍
                    for(int k=i; k<=m; k++) {
                        //把当前列对应行位置扩大tmp倍,然后用每个元素减去,由高斯约当的过程,左边的绝对是0
                        a[j][k]-=a[r][k]*tmp;
                    }
                }
            }
        }

        /*for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                printf(" %.2f ",a[i][j]);
            }
            printf("\n");
        }*/

        /*for(int i=1; i<=r; i++)
            //xi的系数化为1
            ans[i]=a[i][m+1]/a[i][i];*/

        return true;
    }
}

using namespace Gauss_Jordan_Elimination;

int n,m;

int ansmoney=0x3f3f3f3f;
int anscount=0;

int tx[505];

bool iszero(int id) {
    int j=id;
    while(j<=m) {
        if(fabs(a[id][j])>eps) {
            return false;
        } else {
            j++;
        }
    }
    if(j>m) {
        return true;
    }
    return false;
}

void dfs(int dep,int curcount,int curmoney) {
    if(curcount<=anscount&&curmoney>=ansmoney)
        //花更多的钱买了更少的装备,脑子有洞
        return;
    if(dep==0) {
        /*printf("cnt=%d mon=%d\n",curcount,curmoney);
        for(int i=1; i<=n; i++)
            printf(" %d",tx[i]);
        printf("\n-------\n");*/

        /*for(int i=r+1; i<=n; i++) {
            //自由变量能否被表示
            double tmp=0.0;
            for(int j=r+1; i<=n; i++) {
                if(tx[j]>0&&fabs(a[dep][j])>eps) {
                    //该自由变量存在,且属性与这个装备有关
                    tmp+=a[dep][j];
                }
                if(fabs(tmp)<eps) {
                    //该变量能被线性表示
                    tx[dep]=0;
                    dfs(dep-1,curcount,curmoney);
                } else {
                    cout<<"dep="<<dep<<" can't be e"<<endl;
                    tx[dep]=1;
                    dfs(dep-1,curcount+1,curmoney+price[dep]);
                }
            }*/

        if(curcount>anscount) {
            anscount=curcount;
            ansmoney=curmoney;
        } else if(curcount==anscount) {
            ansmoney=min(ansmoney,curmoney);
        }
        return;
    }
    if(!iszero(dep)) {
        //cout<<"dep="<<dep<<" can't be e"<<endl;
        tx[dep]=1;
        dfs(dep-1,curcount+1,curmoney+price[dep]);
    } else {
        //cout<<"dep="<<dep<<" "<<"iszero"<<endl;
        tx[dep]=0;
        dfs(dep-1,curcount,curmoney);
    }
}

int main() {
    scanf("%d%d",&n,&m);
    int minprice=0;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            double tmp;
            scanf("%lf",&tmp);
            a[i][j]=tmp;
        }
    }

    for(int i=1; i<=n; i++) {
        scanf("%d",&price[i]);
        minprice=min(minprice,price[i]);
    }

    gauss_jordan(n,m);

    //已经求矩阵的秩
    //cout<<r<<endl;

    if(r==0){
        printf("0 %d\n",minprice);
    }
    else{
        //给自由变量赋值
        //dfs(n,0,0);

        //直接得到sumprice
        int sumprice=0;
        for(int i=1;i<=r;i++)
            sumprice+=price[i];

        printf("%d %d\n",r,sumprice);
    }
}

double的n*n高斯消元法

虽然经过模板验证,但是不明白为什么可以中途退出。假设我们用来插值,第一个是常数,当某列全为0,那么不是只跟当前变量没有关系吗?建议使用模意义的模板,有数学证明。(虽然这个交上去没有翻过车)

namespace Gauss_Jordan_Elimination{
    const int MAXN=105;
    double a[MAXN][MAXN],del;
    double ans[MAXN];

    const double eps=1e-10;

    bool gauss_jordan(int n){
        for(int i=1;i<=n;i++){
            int mx=i;
            for(int j=i+1;j<=n;j++)
                if(fabs(a[j][i])>fabs(a[mx][i]))
                    mx=j;
            for(int j=1;j<=n+1;j++)
                swap(a[i][j],a[mx][j]);
            if(fabs(a[i][i])<eps)
                return false;
            for(int j=1;j<=n+1;j++){
                if(j!=i){
                    double tmp=a[j][i]/a[i][i];
                    for(int k=i+1;k<=n+1;k++){
                        a[j][k]-=a[i][k]*tmp;
                    }
                }
            }
        }

        for(int i=1;i<=n;i++)
            ans[i]=a[i][n+1]/a[i][i];

        return true;
    }
}


using namespace Gauss_Jordan_Elimination;

模意义下高斯消元法

这里是模质数的高斯消元,假如不是质数,乘法逆元都不一定存在。
https://codeforces.com/contest/1155/problem/E

顺便也发现了一种插值的策略,直接高斯消元然后求出多项式。
这里的高斯消元好像和上面有点不同,但是上面好像是书上类似的写法。
顺带一提,交互题可以写个jury函数,通过传入一个询问来返回一个回答。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const ll p=1000003;

ll inv[1000005];

inline ll mp_init(ll x) {
    if(x>=0&&x<p)
        return x;
    x%=p;
    if(x<0)
        x+=p;
    return x;
}

inline ll mp_add(ll x,ll y) {
    return mp_init(x+y);
}

inline ll mp_sub(ll x,ll y) {
    return mp_init(x-y);
}

inline ll mp_mul(ll x,ll y) {
    return (ll)x*y%p;
}

inline ll qpow(ll x,ll n) {
    ll res=1%p;
    while(n) {
        if(n&1)
            res=res*x%p;
        x=x*x%p;
        n>>=1;
    }
    return res;
}

inline ll get_inv(ll x) {
    return qpow(x,p-2);
}

inline ll mp_div(ll x,ll y) {
    return (ll)x*inv[y]%p;
}


namespace Gauss_Jordan_Elimination_Modulo_Prime {
    const int MAXN=55;
    ll a[MAXN][MAXN];
    ll ans[MAXN];

    bool gauss_jordan(int n) {
        //化为模意义下的数
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n+1; j++)
                a[i][j]=mp_init(a[i][j]);

        for(int i=1; i<=n; i++) {
            //当前是第i列
            int mx=i;
            //当前列的最大变量系数所在的行
            for(int j=i+1; j<=n; j++)
                if(a[j][i]>a[mx][i])
                    mx=j;
            if(a[mx][i]==0) {
                //当当前列最大的行的变量系数都为0,则不影响
                continue;
            }
            if(mx!=i) {
                for(int j=1; j<=n+1; j++)
                    swap(a[i][j],a[mx][j]);
                //交换行,使得当前位置系数最大
            }

            //第j行
            for(int j=1; j<=n+1; j++) {
                if(j!=i) {
                    //消去其他行
                    ll tmp=mp_div(a[j][i],a[i][i]);
                    //该行系数是当前列的tmp倍
                    for(int k=i+1; k<=n+1; k++) {
                        //把这里的k从1开始,可以看见完全消除的结果,但是没必要
                        //把当前列对应行位置扩大tmp倍,然后用每个元素减去
                        a[j][k]=mp_sub(a[j][k],mp_mul(a[i][k],tmp));
                    }
                }
            }
        }


        for(int i=1; i<=n; i++)
            //xi的系数化为1
            ans[i]=mp_div(a[i][n+1],a[i][i]);

        return true;
    }
}


using namespace Gauss_Jordan_Elimination_Modulo_Prime;


ll jury(ll x) {
    ll a[11]= {2,9,1,4,8,10,0,2,8,7,6};
    ll ret=0;
    ll tx=1;
    for(int i=0; i<=10; i++) {
        ret=(ret+tx*a[i]%p)%p;
        tx=tx*x%p;
    }
    return ret;
}

int query(int x) {
    printf("? %d\n",x);
    fflush(stdout);
    scanf("%d",&x);
    //x=jury(x);
    return x;
}

void set_matrix(int id,ll x,ll y) {
    a[id][1]=1;
    for(int i=2; i<=11; i++) {
        a[id][i]=mp_mul(a[id][i-1],x);
    }
    a[id][12]=mp_init(y);
}

ll calc(ll x) {
    ll ret=0;
    ll tx=1;
    for(int i=1; i<=11; i++) {
        ret=(ret+tx*ans[i]%p)%p;
        tx=tx*x%p;
    }
    return ret;
}


void answer(int x) {
    printf("! %d\n",x);
}


int main() {
    for(int i=1;i<1000003;i++){
        inv[i]=get_inv(i);
    }

    for(int i=0; i<=10; i++) {
        int res=query(i);
        if(res==0) {
            /*answer(i);
            exit(0);*/
        }
        set_matrix(i+1,i,res);
    }

    int res=gauss_jordan(11);

    if(res==0) {
        answer(-1);
    } else {

        /*for(int i=1;i<=11;i++){
            printf("%lld ",ans[i]);
        }
        printf("\n");*/

        for(int i=0; i<1e6+3; i++) {
            int r=(int)calc(i);
            if(r==0) {
                answer(i);
                exit(0);
            }
        }
        answer(-1);
    }
}

模2意义下高斯消元法

模2的话,加减法变成异或,乘法变成与,没有除法了(除法按道理说,除以1就是与1,保持不变,除以0……说明这个是自由变量)。
顺便给一个带自由变量的高斯消元,高斯求出来的只是其中一个解,我们可以通过高斯消元消出来的矩阵简化计算,在这种运用中,建议把高斯消元的起点变为1,直观看见消元的结果。

#include<bits/stdc++.h>
#define ll long long
using namespace std;


namespace Gauss_Jordan_Elimination_Modulo_2 {
    const int MAXN=40;
    int a[MAXN][MAXN];
    int ans[MAXN];

    bool gauss_jordan(int n) {
        //化为模意义下的数
        /*for(int i=1; i<=n; i++)
            for(int j=1; j<=n+1; j++)
                a[i][j]=a[i][j]&1;*/

        /*for(int i=1; i<=n; i++) {
            for(int j=1; j<=n+1; j++) {
                printf("%d ",a[i][j]);
            }
            printf("\n");
        }
        printf("---\n");*/

        for(int i=1; i<=n; i++) {
            //当前是第i列
            int mx=i;
            for(int j=i; j<=n; j++)
                if(a[j][i]) {
                    mx=j;
                    break;
                }
            if(a[mx][i]==0) {
                //当当前列最大的行的变量系数都为0,则不影响
                continue;
            }
            if(mx!=i) {
                for(int j=1; j<=n+1; j++)
                    swap(a[i][j],a[mx][j]);
                //交换行,使得当前位置系数最大
            }

            //第j行
            for(int j=1; j<=n+1; j++) {
                if(j!=i&&a[j][i]) {
                    //消去其他行
                    for(int k=/*i+*/1; k<=n+1; k++) {
                        a[j][k]^=a[i][k];
                    }
                }
            }
        }


        /*for(int i=1; i<=n; i++) {
            for(int j=1; j<=n+1; j++) {
                printf("%d ",a[i][j]);
            }
            printf("\n");
        }
        printf("---\n");*/


        for(int i=1; i<=n; i++)
            //xi的系数化为1
            ans[i]=a[i][n+1]&a[i][i];



        /*for(int i=1; i<=n; i++)
            printf("%d ",ans[i]);
        printf("\n");*/
        return true;
    }
}


using namespace Gauss_Jordan_Elimination_Modulo_2;

int n,m,res=0;

int tx[40];

void dfs(int x,int cur) {
    //cout<<"x="<<x<<" res="<<cur<<endl;
    if(cur>=res)
        //最优性剪枝
        return;
    if(x==0) {
        //经过若干调整到达终点,需要检验
        //cout<<"cur="<<cur<<endl;
        for(int i=1; i<=n; i++) {
            int y=0;
            for(int j=1; j<=n; j++) {
                y^=a[i][j]*tx[j];
            }
            //如果这个灯没有前往他要去的目标状态
            if(y!=a[i][n+1]) {
                /*cout<<"fail on r " <<i<<endl;
                for(int i=1; i<=n; i++)
                    printf("%d ",tx[i]);
                printf("\n");*/
                return;
            }
        }
        res=min(res,cur);
        /*printf("res=%d\n",res);
        for(int i=1; i<=n; i++)
            printf("%d ",tx[i]);
        printf("\n");*/
        return;
    }
    if(a[x][x]) {
        //不是自由变量,取值由左下角已经确定了的值来确定
        int y=a[x][n+1];
        //该变量被右端函数值以及前面以及“确定”的值约束
        for(int i=x+1; i<=n; i++) {
            //其实就是y-=a[i]x[i],把已经确定了的x的值乘上他的贡献因数然后减掉
            y^=a[x][i]&tx[i];//第i个变量当前取值是tx,那么现在的x要取什么才会等于y?
            //y=0说明其他变量凑够了
            //cout<<"y="<<y<<endl;
        }
        int ttx=tx[x];
        tx[x]=y;
        dfs(x-1,cur+y);
        tx[x]=ttx;
    } else {
        //当前是自由变量,取值是任意的
        tx[x]=0;
        dfs(x-1,cur);
        //原本的高斯消元后,自由变量的取值都被归一化步骤赋值0,现在赋值1,不一定满足右侧全1,最后需要检验
        tx[x]=1;
        dfs(x-1,cur+1);
        tx[x]=0;
    }
}

int main() {
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++) {
        int u,v;
        scanf("%d%d",&u,&v);
        a[u][v]=1;
        a[v][u]=1;
    }
    for(int i=1; i<=n; i++)
        a[i][i]=a[i][n+1]=1;
    gauss_jordan(n);

    res=0;
    for(int i=1; i<=n; i++) {
        res+=ans[i];
    }

    dfs(n,0);

    printf("%d\n",res);
}

以上是关于模板 - 高斯约旦消元法的主要内容,如果未能解决你的问题,请参考以下文章

题解 P3389 模板高斯消元法

高斯消元——约旦消元法

高斯消元——约旦消元法

高斯消元——约旦消元法

高斯-约旦消元法

luogu P3389 模板高斯消元法