题意
给定一个地图,地图里有黑白格子(背面是其相反颜色格子),需要将黑色的格子全部翻
转为白色的。如果能翻转为白色的,输出最小次数的翻转情况,如果有多个最小次数,
则输出字典序最小的,如果不能翻转输出"IMPOSSIBLE".
思路
1.是否有解:在翻转此位置时,判断此位置上方是否需要翻转,如果需要,则翻转,直
到前n - 1行都翻转成白色,此时判断最后一行是否有黑色格子,如果有,则不满足题意
要求。
2.当前位置是否翻转:首先假设此位置是白色的,被翻转了奇数次后是黑色,偶数次是
白色。用一个数组记录翻转情况,这个位置的翻转情况取决于自身翻转情况 + 四个方向
的翻转情况。
3.找最优解:枚举第一行的翻转成黑色的所有情况。一共有2^m种情况,i从0枚举1<<m,
j取(0, m)个位置是否有1,就需要对位运算足够了解。(i >> j) & 1。从 0 开始枚举
就保证了最小字典序。
时间复杂度:(n * m) * 2m
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 20;
const int inf = 1000;
int mp[maxn][maxn], flip[maxn][maxn], out[maxn][maxn];
int n, m, dir[5][2] = {{0, 0}, {0, -1}, {0, 1}, {1, 0}, {-1, 0}};
/**
判断此位置是否翻转,由四个方向和自身是否翻转过。
先是白色的,翻转偶数次,则是白色,翻转奇数次,则是黑色。
*/
bool check(int x, int y){
int cnt = mp[x][y];
for (int i = 0; i < 5; ++i){
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if (dx < 0 || dy < 0 || dx >=n || dy >= m) continue;
cnt += flip[dx][dy];
}
return cnt & 1;
}
//求每种情况的总共翻转次数
int seek(){
for (int i = 1; i < n; ++i){
for (int j = 0; j < m; ++j){
//前1行的翻转成白色
if(check(i - 1, j)){
flip[i][j] = 1;
}
}
}
int res = 0;
//判断最后一行是否全部为白色。
for (int i = 0; i < m; ++i){
if (check(n - 1, i)){
return inf;
}
}
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
res += flip[i][j];
}
}
return res;
}
void solve(){
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
scanf("%d", &mp[i][j]);
}
}
int tmp = inf;
//穷举第一行的每一种翻转情况,一共有2^m种情况。
for (int i = 0; i < (1 << m); ++i){
memset(flip, 0, sizeof(flip));
//取出每种情况,1对应的位置。
for (int j = 0; j < m; ++j){
flip[0][j] = i >> j & 1;
}
int cnt = seek();
if (tmp > cnt){
tmp = cnt;
for (int ii = 0; ii < n; ii++){
for (int jj = 0; jj < m; jj++){
out[ii][jj] = flip[ii][jj];
}
}
}
}
if (tmp == inf){
puts("IMPOSSIBLE");
return;
}
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
if (j < m - 1) printf("%d ", out[i][j]);
else printf("%d\n", out[i][j]);
}
}
}
int main(){
solve();
return 0;
}