这题很经典啊,以前也遇到过类似的题--计蒜客 硬币翻转。
不过这题不仅要求翻转次数最少,且翻转方案的字典序也要最小。
解法:二进制枚举第一行的翻转方案,然后处理第二行,如果第二行的k列的上一列是黑色,那么第二行k列必须翻转,因为要保证当前行的上一行全为白色。在第一行确定的情况下,当前翻转一定是最优选择。一样的处理方法直到最后一行,最后检查最后一行是否有黑色,如果有说明当前方案无法成功。
PS:枚举第一行是为了解题方便,枚举任何一行都可以,反正每行都能做出最优解。
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 20;
int G[maxn][maxn], old[maxn][maxn];
int n, m;
const int dx[] = {0,0,-1,1,0};
const int dy[] = {1,-1,0,0,0};
void flip(int x, int y){
for(int i = 0; i < 5; ++i){
int px = x + dx[i], py = y + dy[i];
if(px < 0 || py < 0 || px >= n || py >= m) continue;
G[px][py] = 1 - G[px][py];
}
}
int main(){
while(scanf("%d%d", &n, &m) == 2){
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j){
scanf("%d", &old[i][j]);
}
int ans = 1 << 30, a[maxn][maxn];
//memset(a, 1, sizeof(a));
for(int i = 0; i < (1 << m); ++i){
memcpy(G, old, sizeof(G));
int tp = 0, b[maxn][maxn];
memset(b, 0, sizeof(b));
for(int j = 0; j < m; ++j){
if((1 << j) & i) {
b[0][j] = 1;
++tp;
flip(0, j);
}
}
for(int j = 1; j < n ; ++j) {
for(int k = 0; k < m; ++k){
if(G[j - 1][k] == 1){
tp++;
b[j][k] = 1;
flip(j, k);
}
}
}
int flag = 1;
for(int j = 0; j < m; ++j){
if(G[n-1][j]) {
flag = 0;
break;
}
}
if(flag){
int ok = 0;
if(tp < ans) ok = 1;
else if(tp == ans) {
for(int j = 0; j < n; ++j)
for(int k = 0; k < m; ++k){
if(b[j][k] < a[j][k]) {
ok = 1;
j = n, k = m;
}
else if(b[j][k] > a[j][k]){
j = n, k = m;
}
}
}
if(ok) {
ans = tp;
memcpy(a, b, sizeof(b));
}
}
}
//printf("%d\n", ans);
if(ans == 1 << 30) printf("IMPOSSIBLE\n");
else {
for(int i = 0; i < n; ++i) {
for(int j = 0; j < m; ++j){
if(j == 0) printf("%d", a[i][j]);
else printf(" %d", a[i][j]);
}
printf("\n");
}
}
}
return 0;
}
如有不当之处欢迎指出!