题目链接:http://poj.org/problem?id=3279
题目大意:给一个面,1代表黑色,0代表白色,翻一个牌的同时,会把跟他有相邻边的牌一起翻了,问你如何才能在翻最少牌的情况下把他变成全白,如果有多种情况那么输出字典序最小的。
题目思路:对于一行来说,假如他有m个数,那么很明显,这一行一共有2^m种翻转方案。所以我们很显然的想出,可以用0~2^m-1的十进制数,转换成二进制,来代表每一种翻转方案。代码中(i>>j)&1就是来将十进制转换成二进制。然后对于每种翻转情况,很清楚的可以看到,如果确定了第一行,那么后面就能确定。因为第一行已经确定了,那么为了把第一行的黑色变成白色,唯一的方案就是翻转这个黑色块下面的那个牌,所以我们要做的就是,从第二行开始,然后每个都判断一下它上面的是不是黑色,如果是黑色,那么就翻自己,temp[i][j]=1,这个temp是用来记录翻转的方案。那么如何判断上面是不是黑色呢?很简单,简单搜索一下,这里很巧妙的一种思路,首先是自己对应的颜色,+前后左右自己的翻转次数,对2进行取模,如果是0就说明是白色,1就说明是黑色。全部解决完以后,判断一下最后一行的颜色,如果最后一行全是白色的,那么恭喜这个情况是可以的,如果最后一行存在黑块,那么只能凉凉了,因为没有下一行能帮忙了。然后就是判断一下这种情况下翻了几次,如果翻得比当前ans存的小,那么就把这个作为ans,复制到opt里,opt作为最优解,由于我们是从0~2^m-1枚举翻转情况,而且只有在num<ans的时候复制,所以只取最前面的num最小的,直接就能获得字典序最小的情况。
以下是代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
#define inf 0x3f3f3f3f
#define MAXN 20
#define rep(i,a,b) for(int i=a;i<=b;i++)
int n,m,map1[MAXN][MAXN],temp[MAXN][MAXN],opt[MAXN][MAXN];
int dir[5][2]={{1,0},{-1,0},{0,0},{0,1},{0,-1}};
int judge(int x,int y){
int temp2=map1[x][y];
for(int i=0;i<5;i++){
int xx=x+dir[i][0],yy=y+dir[i][1];
if(1<=xx&&xx<=n&&1<=yy&&yy<=m){
temp2+=temp[xx][yy];
}
}
return temp2&1;
}
int cal(){
rep(i,2,n){
rep(j,1,m){
if(judge(i-1,j)){
temp[i][j]=1;
}
}
}
rep(i,1,m){
if(judge(n,i))return -1;
}
int num=0;
rep(i,1,n){
rep(j,1,m){
if(temp[i][j])num++;
}
}
return num;
}
int main(){
while(~scanf("%d%d",&n,&m)){
rep(i,1,n){
rep(j,1,m){
scanf("%d",&map1[i][j]);
}
}
int ans=inf,num;
rep(i,0,(1<<m)-1){
memset(temp,0,sizeof(temp));
rep(j,0,m-1){
temp[1][m-j]=(i>>j)&1;
}
num=cal();
if(num>=0&&num<ans){
ans=num;
memcpy(opt,temp,sizeof(temp));
}
}
if(ans==inf)printf("IMPOSSIBLE\n");
else{
rep(i,1,n){
rep(j,1,m){
printf("%d%c",opt[i][j],j==m?'\n':' ');
}
}
}
}
return 0;
}