acm专题学习之数学(三)高斯消元+Gym - 100008E

题意:给你矩阵大小n×m,让你求出一个0/1矩阵,它的上下左右加上自己1的个数为偶数,且矩阵尽量不为全0。是多case。

条件:

  • 上下左右加上自己1的个数为偶数
  • 矩阵尽量不为全0

思路:会发现只要知道了第一行的排列那么就可以推出剩下所有行,这个时候很容易想到之前学的翻转问题,利用第一行状压枚举,但是会发现m的范围40,2^40暴力是不可能的。这时候会发现既然能够推出下一行,且n+1行全为0,这样就有一个关于第一行为未知数的方程组。这样虽然能够写出方程组,但是还要矩阵尽量不为全0。,所以我们在其中一个自由元放置为1就可以了,也可以把所有的自由元都赋为1。这题里面我把所有的自由元都赋为了1.

高斯消元:高斯消元主要用来求解线性方程组,也可以求解矩阵的秩矩阵的逆。时间复杂度为n^3,主要与方程组的个数,未知数的个数有关。(建议先学习一下线性代数的知识

原理:消元法。也就是我们常说的加减消元回带未知数。

高斯异或消元模板代码:

const int maxn=50;
int equ,var;//行数que,列数var+1(+1多1的地方是方程的b)
int a[maxn][maxn];//增广矩阵(我是从1开始的)
int x[maxn];//解集
int free_x[maxn];//标记是否为不确定的变量元(自由元)
int free_num;//自由元的个数
//返回1代表无解,返回0代表有解
int Gauss()
{
    int max_r,col,k;//max_r表示当前这列绝对值最大的哪一行,col表示当前处理那一列
    free_num=0;
    for(k=1,col=1; k<=equ&&col<=var; k++,col++)
    {
        max_r=k;
        //枚举当前处理的行数
        //找到该col列元素绝对值最大的那行和第k行进行交换(为了在除法的时候减小误差)
        for(int i=k+1; i<=equ; i++)
        {
            if(abs(a[i][col])>abs(a[max_r][col]))
                max_r=i;
        }
        if(a[max_r][col]==0)
        {
            //说明第k行以下全部都为0,继续处理下一行
            k--;
            free_x[free_num++]=col;//记录自由元
            continue;
        }
        if(max_r!=k)
        {
            //与第k行交换
            for(int j=col; j<=var+1; j++)
                swap(a[k][j],a[max_r][j]);
        }
        for(int i=k+1;i<=equ;i++){
            //枚举要删去的行
            if(a[i][col]!=0){
                for(int j=col;j<=var+1;j++)
                    a[i][j]^=a[k][j];
            }
        }
    }
    //无解的情况,化简后的增广矩阵中存在(0, 0, ..., a)这样的列(a != 0).
    for(int i=k;i<=equ;i++){
        if(a[i][col]!=0)
            return -1;
    }
    //自由元有var - k个,解不为一
    if(k<var+1)
        return var-k+1;
    //解是唯一的
    return 0;
}

题目代码:

#include <algorithm>
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=50;
int equ,var;//行数,列数
int a[maxn][maxn];//增广矩阵
int x[maxn];//解集
//int free_x[maxn];//标记是否为不确定的变量元(自由元)
//int free_num;//自由元的个数
//返回1代表无解,返回0代表有解
int Gauss()
{
    int max_r,col,k;//max_r表示当前这列绝对值最大的哪一行,col表示当前处理那一列
    //free_num=0;
    for(k=1,col=1; k<=equ&&col<=var; k++,col++)
    {
        max_r=k;
        //枚举当前处理的行数
        //找到该col列元素绝对值最大的那行和第k行进行交换(为了在除法的时候减小误差)
        for(int i=k+1; i<=equ; i++)
        {
            if(abs(a[i][col])>abs(a[max_r][col]))
                max_r=i;
        }
        if(a[max_r][col]==0)
        {
            //说明第k行以下全部都为0,继续处理下一行
            k--;
            //free_x[free_num++]=col;
            x[col]=1;//自由元赋为1
            continue;
        }
        if(max_r!=k)
        {
            //与第k行交换
            for(int j=col; j<=var+1; j++)
                swap(a[k][j],a[max_r][j]);
        }
        for(int i=k+1;i<=equ;i++){
            //枚举要删去的行
            if(a[i][col]!=0){
                for(int j=col;j<=var+1;j++)
                    a[i][j]^=a[k][j];
            }
        }
    }
    //无解的情况,化简后的增广矩阵中存在(0, 0, ..., a)这样的列(a != 0).
    for(int i=k;i<=equ;i++){
        if(a[i][col]!=0)
            return -1;
    }
    //自由元有var - k个,解不为一
    if(k<var+1)
        return var-k+1;
    //解是唯一的
    return 0;
}
long long int f[maxn][maxn];
void init(){
    memset(a,0,sizeof(a));
    memset(x,0,sizeof(x));
    memset(f,0,sizeof(f));
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        init();
        //状压每个第一行的编号
        for(int i=1;i<=m;i++){
            f[1][i]=(long long int)1<<i;
        }
        //然后异或每个点,看他们影响是来自于那几个第一行的
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                f[i+1][j]=f[i][j-1]^f[i][j]^f[i][j+1]^f[i-1][j];
            }
        }
        //形成增广矩阵
        for(int i=1;i<=m;i++){
            for(int j=1;j<=m;j++){
                if(f[n+1][i]&((long long int)1<<j))
                    a[i][j]=1;
            }
        }
        equ=m;var=m;//矩阵的行equ列var+1
        //printf("sjdaslkf\n");//
        int num=Gauss();
        //-1无解的情况可以不用考虑,因为再怎么样都会有0000的情况是有解的
        //解是唯一说明只能是00000的情况
        //printf("%d\n",num);
        if(num==-1){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    printf("0 ");
                }
                printf("\n");
            }
        }
        else {
            for(int i=equ;i>0;i--){
                if(!x[i]){
                     x[i]=a[i][var+1];
                    for(int j=i+1;j<=var;j++)
                        x[i]^=(a[i][j]&&x[j]);
                }
                f[1][i]=x[i];
            }
            for(int i=2;i<=n;i++){
                for(int j=1;j<=m;j++){
                    f[i][j]=f[i-1][j-1]^f[i-1][j]^f[i-1][j+1]^f[i-2][j];
                }
            }
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    printf("%lld ",f[i][j]);
                }
                printf("\n");
            }
        }
    }
    return 0;
}

总结:总是逃避一些题目,认为只有大佬才能写出来,所以高斯消元这类题之前碰到过却一直未尝试去解决。但这次尝试去学习了,其实也不是很难。面对困难还是不断努力去解决,不要过于害怕和否定自己。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值