(高斯消元)类关灯问题

简单解释:

对于高斯消元,最初的接触是行最简阶梯矩阵的化简。

我认为只分成两个部分:1:向前步骤  2:向后步骤

前者是往下推,后者是回代.

 

In my opinion, pushing down is all about finding the pivot entry, determining the number of free entries, and determining whether there is a solution on the last row.

 

应用(个(se)人(se)总(fa)结(dou)):

1:The number of free elements corresponds to the number of results of certain problems.

for example:

POJ 1830 开关问题

每一个灯有两种状态,分别用0和1表示,当一种灯的状态改变的时候,可能对与它关联的灯的状态造成影响.

我们可以使用0和1首先构造出来一个表示每盏灯状态的列向量X=(x1 x2 x3....xn). 再构造一个系数矩阵 Y=(y1 y2 .... yn)以及一个变换矩阵A[][].满足关系A*Y=X.这里变换矩阵第i行第j列代表的是第j盏灯会改变第i盏灯的状态(1代表i的改变对j有影响,0代表无影响).

(之所以这样构造是因为我们想通过某种方法,求出方程的解,使得这个求出的解刚好就是题目中所要求的。)

系数矩阵存放解的系数,大小为n*1,规定每盏灯最多只能进行一次开关操作(个人认为题面这里说的不是太清楚,应该是x改变引发y改变,此时y的改变不算是一次开关操作),那么就要进行整体操作,A中的j列刚好就是j对1-n的影响,i行算出来的最后结果刚好就是这个灯最后的状态,那么我们可以对于状态改变的灯设置为1,对于状态没有改变的灯的状态设置为0(X矩阵).然后高斯消元就行了.

对于样例1:

                                                                    

                                                                    

存在两个自由变元,每个自由变元有两个数字可以选择,结果为pow(2,自由变元个数)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=50;
void debug()
{
    cout<<"Oh,it's impossible~!!\n";
}
int num1[maxn],num2[maxn];
int n;
int equ,var;
int a[maxn][maxn];
int x[maxn];
int free_x[maxn];
int free_num;

int Gauss()
{
    int max_r,col,k;
    free_num=0;
    for(k=0,col=0;k<equ&&col<var;k++,col++){
        max_r=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--;
            free_x[free_num++]=col;
            continue;
        }
        if(max_r!=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];
                }
            }
        }
    }
    for(int i=k;i<equ;++i){
        if(a[i][col]!=0){
            return -1;
        }
    }
    if(k<var) return var-k;
    for(int i=var-1;i>=0;--i){
        x[i]=a[i][var];
        for(int j=i+1;j<var;++j){
            x[i]^=(a[i][j]&&x[j]);
        }
    }
    return 0;
}

int poww(int x,int y){
    int num=1;
    for(int i=1;i<=y;++i){
        num*=x;
    }
    return num;
}

int main()
{
    int T;
    cin>>T;
    while(T--){
        cin>>n;
        memset(a,0,sizeof(a));
        for(int i=0;i<n;++i){
            scanf("%d",&num1[i]);
        }
        for(int i=0;i<n;++i){
            scanf("%d",&num2[i]);
            if((num1[i]^num2[i])==1){
                a[i][n]=1;//增广列
            }
            a[i][i]=1;
        }
        int u,v;
        while(scanf("%d%d",&u,&v)&&(u+v)){
            a[v-1][u-1]=1;
        }
        equ=n,var=n;
        int t=Gauss();
        if(t==-1){
            debug();
        }
        else{
            printf("%d\n",poww(2,free_num));
        }
    }
    return 0;
}

 

2:Find a solution to this equation

POJ 1222

法1:Gause

5*6的矩阵有30个元素,每个元素看成系数矩阵里面的一个解,那么自然可以想到变换矩阵的大小应该是30*30,增广矩阵的大小为30*31.

对于增广矩阵中的第j列,这一列有30个元素,如果元素i(行)为1就代表j能够对第i盏灯造成影响.....

那么完全可以对5*6=30个元素每个都找到其所对应的行位置的关系.

增广矩阵的最后一列代表的是初始矩阵(之所以没有考虑全部为0的情况是因为0^0=0,0^1=1)

通过增广矩阵求Gause,找到一组解,这一组解就是最后的答案,不过答案的输出是5*6的形式,所以每六个换行就行了.

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=51;
void debug()
{
    cout<<"Oh,it's impossible~!!\n";
}
int num1[maxn],num2[maxn];
int n;
int equ,var;
int a[maxn][maxn];
int x[maxn];
int free_x[maxn];
int free_num;

int Gauss()
{
    int max_r,col,k;
    free_num=0;
    for(k=0,col=0;k<equ&&col<var;k++,col++){
        max_r=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--;
            free_x[free_num++]=col;
            continue;
        }
        if(max_r!=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];
                }
            }
        }
    }
    for(int i=k;i<equ;++i){
        if(a[i][col]!=0){
            return -1;
        }
    }
    if(k<var) return var-k;
    for(int i=var-1;i>=0;--i){
        x[i]=a[i][var];
        for(int j=i+1;j<var;++j){
            x[i]^=(a[i][j]&&x[j]);
        }
    }
    return 0;
}

int poww(int x,int y){
    int num=1;
    for(int i=1;i<=y;++i){
        num*=x;
    }
    return num;
}

int main()
{
    int T,casee=1;
    cin>>T;
    while(T--){
        memset(a,0,sizeof(a));
        memset(x,0,sizeof(x));
        for(int i=0;i<30;++i){
            scanf("%d",&a[i][30]);
        }
        for(int i=0;i<5;++i){
            for(int j=0;j<6;++j){
                int k=i*6+j;
                a[k][k]=1;
                if(i>0){
                    a[k-6][k]=1;
                }
                if(i<4){
                    a[k+6][k]=1;
                }
                if(j>0){
                    a[k-1][k]=1;
                }
                if(j<5){
                    a[k+1][k]=1;
                }
            }
        }
        equ=var=30;
        Gauss();
        printf("PUZZLE #%d\n",casee++);
        for(int i=0;i<5;++i){
            for(int j=0;j<6;++j){
                printf("%d%c",x[i*6+j],j==5?'\n':' ');
            }
        }
    }
    return 0;
}

法2:二进制枚举

第一行有6个元素,一共有2^6中选择的情况,二进制枚举第一行要改变的方式.

然后从第二行开始,每次通过上一行哪个位置有1进行关灯.

最后特判一下最后一行是不是全部都是0,如果是的话就输出,如果不是的话就继续枚举.

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
int mapp[10][10],mappp[10][10];
int putss[10][10];

void judge(int x,int y)
{
    mapp[x][y]^=1;
    if(y==1)
    {
        mapp[x][y+1]^=1;
    }
    else if(y==6)
    {
        mapp[x][y-1]^=1;
    }
    else
    {
        mapp[x][y+1]^=1;
        mapp[x][y-1]^=1;
    }
    if(x==1)
    {
        mapp[x+1][y]^=1;
    }
    else if(x>1&&x<5)
    {
        mapp[x-1][y]^=1;
        mapp[x+1][y]^=1;
    }
    else
    {
        mapp[x-1][y]^=1;
    }
}

void init()
{
    for(int i=1; i<=5; ++i)
    {
        for(int j=1; j<=6; ++j)
        {
            mapp[i][j]=mappp[i][j];
        }
    }
}

void solve()
{
    bool flag=false;
    for(int i=0; i<(1<<6); ++i)
    {
        memset(putss,0,sizeof(putss));
        init();
        for(int j=0; j<6; ++j)
        {
            if(i&(1<<j))
            {
                putss[1][j+1]=1;
                judge(1,j+1);
            }
        }
        for(int i=1; i<5; ++i)
        {
            for(int j=1; j<=6; ++j)
            {
                if(mapp[i][j]==1)
                {
                    putss[i+1][j]=1;
                    judge(i+1,j);
                }
            }
        }
        int num=0;
        for(int i =1; i<=6; ++i)
        {
            if(mapp[5][i]==0)
            {
                ++num;
            }
        }
        if(num==6) flag=true;
        if(flag) break;
    }
    for(int i=1; i<=5; ++i)
    {
        for(int j=1; j<=6; ++j)
        {
            printf("%d%c",putss[i][j],j==6?'\n':' ');
        }
    }
}

int main()
{
    int T;
    cin>>T;
    for(int tt=1; tt<=T; ++tt)
    {
        printf("PUZZLE #%d\n",tt);
        for(int i=1; i<=5; ++i)
        {
            for(int j=1; j<=6; ++j)
            {
                scanf("%d",&mappp[i][j]);
            }
        }
        solve();
    }
    return 0;
}

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值