题目是一道简单的开关问题(插句题外话,记得小时候玩过肯德基制作的一款Flash小游戏,就是根据开关问题来设计的。),要将一个M X N的黑白色相间的格子翻转为白色(翻转可会使指定格子以及其上下左右相邻的格子反色),并求出最优解。
解题思路是先指定第一行格子的翻转方法。并判断下一行与之相邻的格子是否需要翻转(连续翻转两次==不反转)。以此类推。判断最后一行是否全部为白色,如果不是全白则说明无解。
该算法复杂度为O(MN2^n),符合条件。这里注意,7&4=4,而不是返回1,二分DP里是判断是不是为0就行。
// int g=(7>>2&1),f=7&(1<<2);
// cout<<g<<" "<<f;//1101,1,4
#include <iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
#define maxn 16
int tile[maxn][maxn];//每个位置原来的情况
int flip[maxn][maxn];//每个位置的翻转情况
int opt[maxn][maxn];
//枚举第一行的所有翻转状况
//根据第一行,判断下面每行的翻转情况
//写一个判断1个位置是否为白色的情况,原来的值+翻转的值,为偶数则为0
//最后一行确定完后,前m-1行都确保为白色了,只要判断最后一行是否为白色
int dir[5][2]={{0,0},{-1,0},{0,-1},{1,0},{0,1}};
int M,N;
#define inf 1<<29
int judge(int x,int y)
{
int sum=tile[x][y];
int nx,ny;
for(int i=0;i<5;i++)
{
nx=x+dir[i][0];
ny=y+dir[i][1];
if(nx>=0&&nx<M&&ny>=0&&ny<N)
sum+=flip[nx][ny];
}
return sum%2;
}
//第一行的情况确定了,统计翻转次数
int cal()
{
for(int i=1;i<M;i++)
for(int j=0;j<N;j++)
{
if(judge(i-1,j))//如果上面的是黑色,当前位置就要翻转
flip[i][j]=1;
}
//判断最后一行
for(int j=0;j<N;j++)
{
if(judge(M-1,j))
return -1;
}
//统计次数
int all=0;
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
all+=flip[i][j];
return all;
}
//枚举第一行的情况
void solve()
{
int ans=inf;
for(int i=0;i<(1<<N);i++)//2的N次
{
memset(flip,0,sizeof(flip));
for(int j=0;j<N;j++) //如何获得i第j位的值?
{
//j=0时,flip[0][N-1]=i&1,等于flip最高位对应i的最低位
flip[0][N-j-1]=i>>j&1;
//flip[0][j]=i&(1<<j);
// int g=(7>>2&1),f=7&(1<<2);
// cout<<g<<" "<<f;//1101,1,4
}
int num=cal();
if(num>=0&&num<ans)
{
memcpy(opt,flip,sizeof(flip));
ans=num;
}
}
if(ans==inf)
printf("IMPOSSIBLE\n");
else
{
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
printf("%d%c",opt[i][j],j+1==N?'\n':' ');
}
}
int main()
{
while(~scanf("%d%d",&M,&N))
{
for(int i=0;i<M;i++)
for(int j=0;j<N;j++)
scanf("%d",&tile[i][j]);
solve();
}
return 0;
}