题意:给定m*n的一块地面,地面上每个方砖是黑色或者白色的,翻转则使其颜色改变且它的上下左右四个方砖也同时翻转,求能使地面全部为白色的最小翻转次数。如果这个最小翻转次数有多种方案,输出字典序最小的。(好像和搜索没啥关系……)
学会了二进制枚举,在本题中用来枚举第1行的每个方砖翻转/不翻转
第i行(i>1)的翻转方案必须要使得第i-1行全部为白色,故确定第1行的方案之后,整块地面的翻转方案也随之确定
WA原因:
一,刚开始没有考虑到第一行也可以操作,以为方案是唯一的
二、dis数组用于统计某个方案下地面上每一块方砖的主动翻转次数,当方案更改(即第一行方案更改)之后应马上清零
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int map[20][20];//储存初始状态
int dis[20][20];//储存翻转次数
int res[20][20];//储存当前最优结果
int dx[] = { 0, 0, 0, 1, -1 };
int dy[] = { 0, 1, -1,0, 0 };
int m, n, Min = INF;
int flip(int x, int y) {
//由于第一行枚举的是是否翻转,而不是状态本身,需要这个函数推算状态
//每一个格子被其周围四个格子及其本身的(主动)翻转次数决定最终状态
//如果次数之和为奇数,则其有被翻转,否则没有翻转
int color = map[x][y];//最初颜色
int cnt = dis[x][y];
for (int i = 1; i <= 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx<1 || ny<1 || nx>m || ny>n) continue;
cnt+= dis[nx][ny];
}
if (cnt % 2 == 0) return color;
else return !color;
}
int dfs() {
for (int i = 2; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (flip(i - 1, j)) {
//如果同列的上一个格子是黑的,那么这个格子需要主动翻转
dis[i][j]++;
}
}
}
for (int i = 1; i <= n; i++)
if (flip(m, i)) return -1;
int ans = 0;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++) ans += dis[i][j];
return ans;
}
int main()
{
//输入
cin >>m>>n;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
cin >> map[i][j];
}
}
//二进制枚举第一行的翻转情况
//是按字典序枚举的,故答案一定符合字典序最小
for (int i = 0; i < (1 << n); i++) {
memset(dis, 0, sizeof(dis));
//记得清空!!不清空就暴毙了
//共有1<<n种翻转方法,在每一种方法的基础上翻转全部
for (int j = 1; j <= n; j++) {//决定第一行每一个格子翻还是不翻
dis[1][j] = (i >> (j - 1)) & 1;//n-j+1表示从右往左数的第j个格子
}
int ans = dfs();//从第二行开始翻转全部
if (ans >= 0 && ans < Min) {
Min = ans;//维护最小答案
memcpy(res, dis, sizeof(dis));
}
}
if (Min == INF) cout << "IMPOSSIBLE";
else {
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
cout << res[i][j] << " ";
}
cout << endl;
}
}
return 0;
}