D - Fliptile POJ - 3279
题意: 给一个m*n的01矩阵,对某一块砖踩一脚,1->0 || 0->1, 求将整个举证全部变为0最少踩几次
典型的翻转问题,此类问题的特点
- 要么翻1次要么不翻,其余情况重复了
- 与先翻了哪一块无关,只考虑最终翻没翻即可
利用以上性质,为了方便搜索,我们枚举出第一行的所有可能情况(注意如何按照字典序枚举),则第一行决定了第二行哪些格子必须被踩,因为如果不踩第一行将存在 1 不满足,之后几行同理。对最后一行也完成所有操作后,判断最后一行是否全为0,是则找出一组答案。
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 17;
int m, n;
bool mp[maxn][maxn], ans[maxn][maxn], turn[maxn][maxn];
int cnt, res;
int dx[] = {1,-1,0,0,0};
int dy[] = {0,0,1,-1,0};
bool color(int x, int y) {
int tmp = mp[x][y], nx, ny;
for(int i = 0; i < 5; i++) {
nx = x + dx[i]; ny = y + dy[i];
if(nx <= m && nx >= 1 && ny >= 1 && ny <= n)tmp += turn[nx][ny];
}
return tmp&1; // 奇数次则为黑
}
void dfs() {
for(int i = 2; i <= m; i++) {
for(int j = 1; j <= n; j++) {
if(color(i-1, j)) { // 上一行砖为黑色则需要反转当前位置
turn[i][j] = 1;
cnt++;
}
if(res <= cnt) return ; // 最优化剪枝
}
}
for(int j = 1; j <= n; j++) {
if(color(m, j)) return ;
}
res = cnt;
memcpy(ans, turn, sizeof(turn));
}
void print() {
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
printf("%d ", ans[i][j]);
}
printf("\n");
}
}
int main() {
// freopen("test.in", "r", stdin);
while(~scanf("%d%d", &m , &n)) {
res = INF;
memset(ans, 0, sizeof(ans));
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
scanf("%d", &mp[i][j]);
// 处理第一行所有可能情况
for(int j = 0; j < (1 << n); j++) {
cnt = 0;
memset(turn, 0, sizeof(turn));
for(int i = 1; i <= n; i++) {
turn[1][i] = (j >> (n-i)) & 1; // 按字典序查找第一行所有情况,不懂的话可以用下面输出或者手动模拟
// cout << turn[1][i] << " ";
if(turn[1][i]) cnt++;
}
dfs();
// cout << endl;
}
if(res==INF) printf("IMPOSSIBLE\n");
else print();
}
return 0;
}