题意:
有一个n*m的格子,每个格子都有黑白两面(0表示白色,1表示黑色)。我们需要把所有的格子都反转成黑色,每反转一个格子,它上下左右的格子都会跟着反转。请求出用最小步数完成反转时每个格子反转的次数。有多个解时,输出字典序最小的一组。
思路:
**枚举第一行的所有翻转策略,逐个测试是否可行。**每个翻转策略是一个01串,比如010001表示第2位和第6位翻转,一共有m位,所以第一行的翻转策略数一共2^m个。
然后从第二行起,从上到下搜索,当前行是否需要反转取决于上一行的状态,通过翻转当前行使上一行为0,而不是通过上一行翻转为0后,看当前行的状态判断自己是否需要翻转,否则还会继续影响上一行。当前行是否需要反转取决于上一行的是否为黑色.
保证字典序最小:从小到大枚举就保证了字典序。
参考:https://www.cnblogs.com/caitian/p/5396946.html
#include <cstdio>
#include <cstring>
#include <algorithm>
#define first fi
#define second se
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int maxn = 20;
int move[5][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}, {0,0}};
int G[maxn][maxn];
int flip[maxn][maxn], opt[maxn][maxn];
int n,m;
// (i,j)位置的颜色.1-black,0-white.
int color(int i, int j){
int c = G[i][j];
for(int k = 0; k < 5; ++k){
int x = i + move[k][0];
int y = j + move[k][1];
if(x >= 0&&x < n&&y >= 0&&y < m) c+= flip[x][y];
}
return c&1;
}
int try_this(){
for(int i = 1; i < n; ++i){ //从第二行开始
for(int j = 0; j < m; ++j){
if(color(i-1,j) != 0) flip[i][j] = 1; //如果上一行G[i-1][j]是黑色,必则须翻转(i,j)
}
}
for(int i = 0; i < m; ++i){
if(color(n-1,i) != 0) return -1; //判断最后一行是否全白,否则这个方案不可行
}
int cnt = 0; // 统计翻转次数
for(int i = 0; i < n; ++i){
for(int j = 0; j < m; ++j){
cnt+= flip[i][j];
}
}
return cnt;
}
int solve(){
//枚举第一行的翻转
int ans = -1;
for(int i = 0; i < (1<<m); ++i){
memset(flip, 0, sizeof(flip));
for(int k = 0; k < m; ++k){
flip[0][m-k-1] = (i>>k)&1;
}
int num = try_this();
if(num >= 0&&(ans < 0||ans > num)){
ans = num;
memcpy(opt, flip, sizeof(flip));
}
}
return ans;
}
int main()
{
freopen("in.txt","r",stdin);
while(scanf("%d %d",&n,&m) == 2){
for(int i = 0; i < n; ++i){
for(int j = 0; j < m; ++j)
scanf("%d",&G[i][j]);
}
int ans = solve();
if(ans == -1) printf("IMPOSSIBLE\n");
else {
for(int i = 0; i < n; ++i){
for(int j = 0; j < m; ++j){
printf("%d",opt[i][j]);
if(j < m-1) printf(" ");
}
printf("\n");
}
}
}
return 0;
}