POJ1222 EXTENDED LIGHTS OUT 高斯消元

题目链接:http://poj.org/problem?id=1222


题目大意:给你一个5*6 的矩阵,矩阵里每一个单元都有一个灯和一个开关,如果按下此开关,那么开关所在位置的那个灯和开关前后左右的灯的状态都会改变(即由亮到不亮或由不亮到亮)。给你一个初始的灯的状态,问怎样控制每一个开关使得所有的灯最后全部熄灭(此题保证有唯一解)。


分析:高斯消元。很显然每个灯最多只需要按1 下(因为按两下和没有按是一个效果)。我们可以定义30 和未知x0、x1.......x29 代表每一个位置的开关是否被按。那么对于每一个灯的状态可以列一个方程,假设位置(i,j)处的开关为x(i*6+j),那么我们就可以列出方程:x(i*6+j)+x((i-1)*6+j)+x((i+1)*6+j)+x(i*6+j-1)+x(i*6+j+1) =bo(mod 2)(括号里的数字为x 的下标,这里假设这些下标都是符合要求的,即都在矩形内,如果不在则可以去掉,当这个灯初始时是开着的,那么bo 为1,否则为0)这样可以列出30 个方程,然后用高斯消元解这个方程组即可。


实现代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=30;
int a[maxn][maxn+1],x[maxn];//a是系数矩阵和增广矩阵,x存放最后的解
int equ,var,free_n;//equ是系数矩阵的行数,var个变元(即系数矩阵的列数)
void Init()
{
    memset(a,0,sizeof(a));
    memset(x,0,sizeof(x));
    for(int i=0;i<5;i++)
      for(int j=0;j<6;j++)
      {

          if(i>0) a[ (i-1)*6+j ][i*6+j]=1;
          if(i<4) a[ (i+1)*6+j ][i*6+j]=1;
          if(j>0) a[ i*6+(j-1) ][i*6+j]=1;
          if(j<5) a[ i*6+(j+1) ][i*6+j]=1;
          a[i*6+j][i*6+j]=1;
          scanf("%d",&a[i*6+j][30]);
      }
}
int gcd(int a,int b)
{
    if(a<0) return gcd(-a,b);
    if(b<0) return gcd(a,-b);
    return b==0?a:gcd(b,a%b);
}
void Gauss()
{
    int k,col=0; //当前处理的列
    for(k=0;k<equ&&col<var;k++,col++)
    {
        int mx=k;
        for(int i=k+1;i<equ;i++)
          if(a[i][col]>a[mx][col]) mx=i;
        if(mx!=k)
          for(int i=k;i<var+1;i++) swap(a[k][i],a[mx][i]);
        if(!a[k][col]) { k--; continue; }
        for(int i=k+1;i<equ;i++)
          if(a[i][col]!=0)
          {
              int lcm=a[k][col] / gcd(a[k][col],a[i][col]) * a[i][col];
              int ta=lcm/a[i][col], tb=lcm/a[k][col];
              if(a[i][col]*a[k][col]<0) tb=-tb;
              for(int j=col;j<var+1;j++)
                a[i][j]=( (a[i][j]*ta)%2 - (a[k][j]*tb)%2 +2 )%2;
          }
    }
    //Debug();
    /*
    for(int i=k;i<equ;i++)
      if(a[i][var]) return -1; //无解
    */
    for(int i=0,j;i<equ;i++) //每一行主元素化为非零
      if(!a[i][i])
      {
          for(j=i+1;j<var;j++)
            if(a[i][j]) break;
          if(var==j) break;
          for(int r=0;r<equ;r++) swap(a[r][i],a[r][j]);
      }
    for(int i=k-1;i>=0;i--)
    {//从消元后主对角线非零的最后一行,往前回带
        int tmp=a[i][var]%2;
        for(int j=i+1;j<var;j++)
          if(a[i][j]) tmp=(tmp-a[i][j]*x[j]%2+2)%2;
        x[i]=(tmp/a[i][i])%2;
    }
}
int main()
{
    int t,T=1;
    scanf("%d",&t);
    equ=30,var=30;
    while(t--)
    {
        Init();
        Gauss();
        printf("PUZZLE #%d\n",T++);
        for(int i=0;i<30;i++)
        {
            if(i%6!=0) putchar(' ');
            printf("%d",x[i]);
            if(i%6==5) puts("");
        }
    }
    return 0;
}



/*
poj 1222 用求逆元的做法
高斯消元的作用:1、求解方程组  2、可以计算矩阵的逆
AX=B;X=A^(-1)*B;懂了吗?
先求出逆A1,然后直接计算  A1*B  就是解
*/
//高斯消元  用逆元来计算,节省时间
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int a[31][61],b[31][61],c[31][31];//系数矩阵
int ans[31],w[31];
void gauss()//消元
{
    for(int i=0; i<30; i++) //i代表列,也是主元的位置
    {
        int k=i;//k代表行,从对角线的行开始就行
        for(; k<30; k++)
        if(a[k][i]!=0) //找到这列第1个不为0的行,好做主元啊
          break;
        for(int j=0;j<=60;j++) //交换行
          swap(a[i][j],a[k][j]); //开始消元
        for(int j=0;j<30;j++)    //j代表行
          if(i!=j&&a[j][i])  //不是主元行,要消的行已经是1才消
            for(int k=0; k<=60; k++)   //k代表列
              a[j][k]=a[i][k]^a[j][k];
        //消元        
    }
}
int init()
{
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    for(int i=0; i<30; i++)
    {
        b[i][i]=1;
        if(i%6!=0) b[i-1][i]=1;
        if(i%6!=5) b[i+1][i]=1;
        if(i>5) b[i-6][i]=1;
        if(i<24) b[i+6][i]=1;
    }
    int tp=0;
    for(int i=30; i<=59; i++) //右边放一个单位阵
      b[tp++][i]=1;
    return 0; 
}
int main()
{
    int t,tt=0;
    init();
    for(int i=0; i<30; i++)
      for(int j=0; j<60; j++)
        a[i][j]=b[i][j];
    gauss();
    for(int i=0; i<30; i++)
       for(int j=0; j<30; j++)
         c[i][j]=a[i][j+30];//计算逆元
    scanf("%d",&t);
    while(t--)    
    {
        for(int i=0; i<30; i++)
        {
            scanf("%d",&w[i]);
            ans[i]=0;
        }
        for(int i=0; i<30; i++)
          for(int j=0; j<30; j++)
            ans[i]=ans[i]^c[i][j]*w[j];
          //直接矩阵相乘,加法就是异或
          //gauss();
          //for(int j=0;j<30;j++)
          // ans[j]=a[j][30];
        printf("PUZZLE #%d\n",++tt);
        for(int i=0; i<30; i++)
        {
            printf("%d",ans[i]);
            if(i%6==5) printf("\n");
            else printf(" ");                
        }        
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值