原题链接:poj3279 Fliptile
题意:可以看作一个M*N的棋盘,棋盘中布满黑白色的棋子,黑色翻转后是白色,白色反转是黑色,每步操作翻转一个棋子,并且其上下左右也都跟着翻转。
求:尽可能少的步数把所有的棋子翻成白色,最小步数有多个时,输出字典序最小的一组,无解时输出IMPOSSIBLE
输入:M N 棋子颜色(0代表白色,1代表黑色)
输出:每个棋子对应的最小步数
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX_N = 16;
const int MAX_M = 16;
const int dex[2][5] = { {-1, 0, 0, 0, 1},
{0, -1, 0, 1, 0}};
//输入数据
int M, N;
int tile[MAX_M][MAX_N];
int opt[MAX_M][MAX_N]; //保存最优解
int flip[MAX_M][MAX_N]; //保存中间值
int get(int x, int y){ //查询(x,y)的颜色
int cnt = tile[x][y];
for(int i = 0;i < 5;i ++){
int dx = x + dex[0][i], dy = y + dex[1][i];
if(dx >= 0 && dx < M && dy >= 0 && dy < N){
cnt += flip[dx][dy];
}
}
return cnt % 2;
}
int calc(){ //求出第1行确定的情况下的最小操作次数,不存在是返回-1
int i, j;
for(i = 1;i < M;i ++){ //从第2行开始翻转
for(j = 0;j < N;j ++){
if(get(i - 1, j) != 0){ //上一行(i-1,j)如果是黑色,(i,j)需要翻转
flip[i][j] = 1;
}
}
}
for(j = 0;j < N;j ++){ //最后一行是否全白
if(get(M - 1, j) != 0){ //如果有黑色,则无解
return -1;
}
}
int res = 0; //统计翻转的次数
for(i = 0;i < M;i ++){
for(j = 0;j < N;j ++){
res += flip[i][j];
}
}
return res;
}
int main(){
int i, j;
while(~scanf("%d%d", &M, &N)){
for(i = 0;i < M;i ++){ //输入
for(j = 0;j < N;j ++)
scanf("%d", &tile[i][j]);
}
int ans = -1;
for(i = 0;i < 1 << N;i ++){ //按照字典序尝试第一行的所有可能性
memset(flip, 0, sizeof(flip));
for(j = 0;j < N;j ++){
flip[0][N - j - 1] = i >> j & 1;
}
int num = calc();
if(num >= 0 && (ans < 0 || num < ans)){
ans = num;
memcpy(opt, flip, sizeof(flip));
}
}
if(ans < 0){ //-1时无解
printf("IMPOSSIBLE\n");
}else{
for(i = 0;i < M;i ++){
for(j = 0;j < N;j ++)
printf("%d%c", opt[i][j], j + 1 == N ? '\n' : ' ');
}
}
}
return 0;
}
代码来源于:挑战程序设计竞赛(第2版)P154