Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 5383 | Accepted: 3557 |
Description
Input
Output
Sample Input
2 0 1 1 0 1 0 1 0 0 1 1 1 0 0 1 0 0 1 1 0 0 1 0 1 0 1 1 1 0 0 0 0 1 0 1 0 1 0 1 0 1 1 0 0 1 0 1 1 1 0 1 1 0 0 0 1 0 1 0 0
Sample Output
PUZZLE #1 1 0 1 0 0 1 1 1 0 1 0 1 0 0 1 0 1 1 1 0 0 1 0 0 0 1 0 0 0 0 PUZZLE #2 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 1 1 0 1 0 1 1 0 1 1 0 1
Source
题目大意就不说了,就是把棋盘上的1全变0即可
如果枚举的话,看似有2的30次方中可能,其实不是。
实际上只需要枚举第一行的状态即可,再往后,如果想要解决问题,必须根据第一行的状态推下去。
对于每个位置,如果上一行的这一列有1,必然这个按键要按下去,不然不可能达到要求的结果。
枚举代码如下,直接使用二进制枚举
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int map[5][6],g[5][6]; int res[5][6]; void press(int i,int j){ g[i][j]=!g[i][j]; if(i>0) g[i-1][j]=!g[i-1][j]; if(j>0) g[i][j-1]=!g[i][j-1]; if(i<4) g[i+1][j]=!g[i+1][j]; if(j<5) g[i][j+1]=!g[i][j+1]; } bool ok(){ for(int j=0;j<6;j++) if(g[4][j]) return 0; return 1; } void solve(){ for(int k=0;k<64;k++){ memcpy(g,map,sizeof(map)); memset(res,0,sizeof(res)); for(int j=0;j<6;j++) if(k&(1<<j)){ press(0,j); res[0][j]=1; } for(int i=1;i<5;i++) for(int j=0;j<6;j++) if(g[i-1][j]){ press(i,j); res[i][j]=1; } if(ok()){ for(int i=0;i<5;i++){ for(int j=0;j<5;j++) printf("%d ",res[i][j]); printf("%d\n",res[i][5]); } break; } } } int main(){ //freopen("input.txt","r",stdin); int t,cases=0; scanf("%d",&t); while(t--){ for(int i=0;i<5;i++) for(int j=0;j<6;j++) scanf("%d",&map[i][j]); printf("PUZZLE #%d\n",++cases); solve(); } return 0; }
2,高斯消元
不得不说这是一种逆向思想,题目要求是怎样能全灭掉,建立方程则是求解怎样能从全灭转变到当前状态,实际上,由于一个灯摁两次的效果是一样的,所以这样求出来的解就是最终解。
然后求解的时候,最好还是用异或操作。这样就避免出现了负数和特别巨大的数字。经试验,这是很有可能的。
这就是用高斯消元法求异或方程组了
异或方程组就是形如这个样子的方程组:
M[0][0]x[0]^M[0][1]x[1]^…^M[0][N-1]x[N-1]=B[0]
M[1][0]x[0]^M[1][1]x[1]^…^M[1][N-1]x[N-1]=B[1]
…
M[N-1][0]x[0]^M[N-1][1]x[1]^…^M[N-1][N-1]x[N-1]=B[N-1]
其中“^”表示异或(XOR, exclusive or),M[i][j]表示第i个式子中x[j]的系数,是1或者0。B[i]是第i个方程右端的常数,是1或者0。
解这种方程可以套用高斯消元法,只须将原来的加减操作替换成异或操作就可以了,两个方程的左边异或之后,它们的公共项就没有了。
具体的操作方法是这样的:对于k=0..N-1,找到一个M[i][k]不为0的行i,把它与第k行交换,用第k行去异或下面所有M[i][j]不为0的行i,消去它们的第k个系数,这样就将原矩阵化成了上三角矩阵;最后一行只有一个未知数,这个未知数就已经求出来了,用它跟上面所有含有这个未知数的方程异或,就小觑了所有的着个未知数,此时倒数第二行也只有一个未知数,它就被求出来了,用这样的方法可以自下而上求出所有未知数。
高斯消元介绍: http://blog.csdn.net/shiren_Bod/article/details/5766907
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int map[5][6]; int g[35][35],res[35]; int n; int Gauss(){ int pos; for(int k=0;k<n;k++){ pos=-1; for(int i=k;i<n;i++) //找到这一列中第一个非0元素的行 if(g[i][k]!=0){ pos=i; break; } for(int i=k;i<=n;i++) swap(g[k][i],g[pos][i]); //交换位置,我们在学线性代数的时候也经常这么做 for(int i=k+1;i<n;i++){ if(g[i][k]==0) continue; for(int j=k;j<=n;j++) //对于下面出现的这一列中有1的行,需要把1消掉 g[i][j]^=g[k][j]; } } for(int i=n-1;i>=0;i--){ for(int j=0;j<i;j++){ if(g[j][i]==0) continue; for(int k=0;k<=n;k++) g[j][k]^=g[i][k]; } int flag=0; for(int j=0;j<n;j++) if(g[i][j]){ flag=1; break; } if(!flag) //如果某一行的系数全是0,说明就不对了 return 0; res[i]=g[i][n]; } return 1; } int main(){ //freopen("input.txt","r",stdin); int t,cases=0; scanf("%d",&t); n=30; while(t--){ memset(g,0,sizeof(g)); for(int i=0;i<5;i++) for(int j=0;j<6;j++){ scanf("%d",&map[i][j]); if(map[i][j]) g[6*i+j][n]=1; } for(int i=0;i<5;i++) for(int j=0;j<6;j++){ g[i*6+j][i*6+j]=1; if(i>0) g[6*i+j][6*(i-1)+j]=1; if(j>0) g[6*i+j][6*i+j-1]=1; if(i<4) g[6*i+j][6*(i+1)+j]=1; if(j<5) g[6*i+j][6*i+j+1]=1; } printf("PUZZLE #%d\n",++cases); Gauss(); for(int i=0;i<5;i++) for(int j=0;j<6;j++) printf("%d%c",res[i*6+j],j==5?'\n':' '); } return 0; }