Fliptile
题目链接:POJ - 3279题意:M*N的格子, 每个小格子边长为单位长度1, 0表示格子为白色, 1表示黑色, 每次翻转一个格子可以使它本身和四周的四个格子由1变0, 或由0变1;最后要使全部格子都是白色, 即都为0;
每个格子最多翻转一次, 若翻转两次, 便又回到了之前的状态, 所以应该有2^(n*m)种操作方式;m和n的范围是1~15, 所以如果直接爆搜的话,,,2^225, 会跑到下一次宇宙大爆炸???
换个思想, 对于第一行, 有2^n种操作, 处理完第一行后, 看下一行, 如果此行的上一行的对应位置是1, 那么一定要把此位置翻转, 因为此时上一行已经处理完,只有该位置翻转才能改变上一行的对应位置, 依次处理, 直到处理完最后一行结束;
在判断最后一行是否已经全为0(此时上边的每一行都已处理完, 且不会有1), 若是, 那么这是一种可行操作, 记录下来, 最后输出操作次数最小的;
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int G[20][20], ans, m, n, f[20][20], p[20][20], q[20][20];
void flip(int i, int j){//翻转i,j位置,本身和四周方格同时翻转;
f[i][j]=!f[i][j];
if(i+1<=m)
f[i+1][j]=!f[i+1][j];
if(i-1>0)
f[i-1][j]=!f[i-1][j];
if(j+1<=n)
f[i][j+1]=!f[i][j+1];
if(j-1>0)
f[i][j-1]=!f[i][j-1];
}
int Count(int k){//第一行操作状态为k时, 已经操作的次数;
int cnt=0;
while(k){
cnt+=k&1;
k>>=1;
}
return cnt;
}
int main(){
scanf("%d%d", &m, &n);
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
scanf("%d", &G[i][j]);
}
}
int res=0, w=(1<<n);
ans=INF;
for(int k=0; k<w; k++){
res=Count(k);
memset(q, 0, sizeof(q));
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
f[i][j]=G[i][j];
}
}
for(int j=1; j<=n; j++){
if((1<<(j-1))&k){//对应位置是否翻转;
flip(1, j);
q[1][j]=1;
}
}
for(int i=2; i<=m; i++){
for(int j=1; j<=n; j++){
if(f[i-1][j]){
flip(i, j);
q[i][j]=1;
res++;
}
}
}
int flag=0;
for(int j=1; j<=n; j++){
if(f[m][j]){
flag=1;
break;
}
}
if(flag) continue;
if(res<ans){
ans=res;
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
p[i][j]=q[i][j];
}
}
}
}
if(ans>=INF) printf("IMPOSSIBLE\n");
else
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
printf("%d%c", p[i][j], j==n?'\n':' ');
}
}
return 0;
}