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