D - Fliptile 二进制枚举

D Fliptile

题意:给定m*n的一块地面,地面上每个方砖是黑色或者白色的,翻转则使其颜色改变且它的上下左右四个方砖也同时翻转,求能使地面全部为白色的最小翻转次数。如果这个最小翻转次数有多种方案,输出字典序最小的。(好像和搜索没啥关系……)

学会了二进制枚举,在本题中用来枚举第1行的每个方砖翻转/不翻转
第i行(i>1)的翻转方案必须要使得第i-1行全部为白色,故确定第1行的方案之后,整块地面的翻转方案也随之确定

WA原因:
一,刚开始没有考虑到第一行也可以操作,以为方案是唯一的
二、dis数组用于统计某个方案下地面上每一块方砖的主动翻转次数,当方案更改(即第一行方案更改)之后应马上清零

#include
#include
#include
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<
		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;
}

你可能感兴趣的:(搜索)