题意:给你一个n*m的黑白棋盘,问能否通过操作使得棋盘全变为白色。如果有求一种操作次数最少的方法(相同时要求字典序最小),没有输出"IMPOSSIBLE"。操作为:将某一块和它上下左右全部翻转。
思路:由于我们每次反转都会影响到上面的棋子,我们想全变为白色,我们需要从第二行到第n行每一行把它的上一行的黑色块翻转为白色,如果最后一行把上一行的翻转为白色之后最后一行还有黑色块,证明这种方法不能将棋盘全变白。我们可以发现只要第一行的状态一定,下面的翻法也就一定了。所以我们只需要枚举第一行的所有翻法就可以,我们可以通过用枚举二进制数字的方法来表示翻法。例如:n=5,m=5。00101表示第三块和最后一块进行翻转,此时二进制数最大为11111即(1<<5)-1。
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF = 1e9 + 5;
const int MAXN = 20;
int n, m;
bool MAP[MAXN][MAXN], ans[MAXN][MAXN];//地图和答案
bool t[MAXN][MAXN], vis[MAXN][MAXN];//临时图和临时答案
void FLIPS(int x, int y)//翻
{
t[x][y] = !t[x][y];//自己
t[x - 1][y] = !t[x - 1][y];//上
t[x + 1][y] = !t[x + 1][y];//下
t[x][y - 1] = !t[x][y - 1];//左
t[x][y + 1] = !t[x][y + 1];//右
}
bool judge()//检查最后一行
{
for(int i = 1; i <= m; i++)
{
if(t[n][i] == 1) return false;//有黑色的
}
return true;
}
int work(int cnt)//2-n行翻转
{
for(int i = 2; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(t[i - 1][j] == 1)//上一行的j位置为黑,那么就要翻转这一块
{
vis[i][j] = true;
FLIPS(i, j);
cnt++;
}
}
}
if(judge()) return cnt;//最后一行全为白色
return INF;
}
int main()
{
while(cin >> n >> m)
{
//输入
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
cin >> MAP[i][j];
}
}
//求解
int MIN = INF;//存储步数最小值
for(int i = 0; i < (1 << m); i++)//枚举第一行踩的方法
{
memcpy(t, MAP, sizeof(t));//MAP赋给一个临时图t
memset(vis, 0, sizeof(vis));//vis表示踩哪
int cnt = 0;//统计这种方案下需要操作翻转的次数
for(int j = 0; j < m; j++)//对第一行进行操作
{
if(i&(1 << j))
{
vis[1][j + 1] = true;
FLIPS(1, j + 1);
cnt++;
}
}
//work推出下面2-n行的翻转情况
if(work(cnt) < MIN)//更新步数和答案
{
MIN = cnt;
memcpy(ans, vis, sizeof(vis));
}
}
//cout << endl;
if(MIN == INF)//没答案
{
printf("IMPOSSIBLE\n");continue;
}
//输出答案
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
cout << ans[i][j] << " ";
}
cout << endl;
}
}
return 0;
}
/*
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
*/