题目链接:http://poj.org/problem?id=3279
此题在kuangbin带你飞专题一做的,开始把它叫做母牛翻转问题,但是第一次基本上都是按着别人的代码写的,现在又在理解的基础上把它重新敲了一遍。
思路:枚举第一行的翻转状态,然后根据翻转要求,要使第一行剩下的1变成0,则只能翻转第二行来使第一行余下的1变成0,且只能是正下方相邻的那一个。这样依次往下翻转每一行,最后是判断最后一行翻转后是不是全部为0即可。由于每个点翻转奇数次的效果相同,每个点翻转偶数次效果相同,所以要使翻转次数最小,则每个点只能翻或者不翻,这就是第一行的翻转状态能用二进制来枚举的原因。
不解之处:题目要求是小翻转次数和相应的字典序,这里只是找到解就输出了,为什么这样一定是满足要求的呢?
代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define LL __int64
#define INF 0xffffffff
using namespace std;
int A[20][20],vis[20][20],B[20][20];
int M,N;
int d[][2]={1,0,-1,0,0,1,0,-1,0,0};
int isok(int s){
memset(vis,0,sizeof(vis));
memcpy(B,A,sizeof(B));//把A数组拷贝给B数组
for(int i=0;i<N;i++)//枚举第一行的翻转状态
if(s&(1<<i)){//该位置为1,则翻转改点及其四周的点
B[0][i]^=1;B[1][i]^=1;vis[0][i]=1;
if(i>0) B[0][i-1]^=1;
if(i<N-1) B[0][i+1]^=1;
}
for(int i=1;i<M;i++)//这里道理同上
for(int j=0;j<N;j++)
if(B[i-1][j]){
vis[i][j]=1;
for(int p=0;p<5;p++){
int x=d[p][0]+i,y=d[p][1]+j;
if(x<0 || y<0 || x>=M || y>=N) continue;
B[x][y]^=1;
}
}
for(int i=0;i<N;i++) if(B[M-1][i]) return 0;//判断最后一行是不是全为0
return 1;
}
int main(){
//freopen("D:\\in.txt","r",stdin);
while(cin>>M){
cin>>N;
for(int i=0;i<M;i++) for(int j=0;j<N;j++)
scanf("%d",&A[i][j]);
int op=0;
for(int i=0;i<(1<<N);i++)//枚举第一行的翻转状态,用一个整数表示
if(isok(i)){//判断是否有解
op=1;break;
}
if(op){
for(int i=0;i<M;i++) for(int j=0;j<N;j++)
cout<<vis[i][j],j==N-1? cout<<endl:cout<<" ";
}
else cout<<"IMPOSSIBLE\n";
}
return 0;
}