题目链接: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;
}