POJ 3279 Fliptile

本文介绍了一个涉及网格翻转的问题,目标是最小化翻转次数使所有方格变为白色。通过二进制枚举和动态规划的方法,实现了有效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Farmer John knows that an intellectually satisfied cow is a happy cow who will give more milk. He has arranged a brainy activity for cows in which they manipulate an M× N grid (1 ≤ M ≤ 15; 1 ≤ N ≤ 15) of square tiles, each of which is colored black on one side and white on the other side.

As one would guess, when a single white tile is flipped, it changes to black; when a single black tile is flipped, it changes to white. The cows are rewarded when they flip the tiles so that each tile has the white side face up. However, the cows have rather large hooves and when they try to flip a certain tile, they also flip all the adjacent tiles (tiles that share a full edge with the flipped tile). Since the flips are tiring, the cows want to minimize the number of flips they have to make.

Help the cows determine the minimum number of flips required, and the locations to flip to achieve that minimum. If there are multiple ways to achieve the task with the minimum amount of flips, return the one with the least lexicographical ordering in the output when considered as a string. If the task is impossible, print one line with the word "IMPOSSIBLE".

Input
Line 1: Two space-separated integers:  M and  N 
Lines 2..  M+1: Line  i+1 describes the colors (left to right) of row i of the grid with  N space-separated integers which are 1 for black and 0 for white
Output
Lines 1..  M: Each line contains  N space-separated integers, each specifying how many times to flip that particular location.
Sample Input
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
Sample Output
0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0
 
 
 
 
题意:给M行N列的数,由0,1组成,有一种翻转的操作,每次翻转可以令其本身还有上下左右的所有块都翻转一次,问你最后将所有块都翻转成白色的这种方案是什么,把方案输出出来,要求翻转的块为1,不翻转的为0,有多个满足的结果,要求翻转的块数最小,并且按照字典序;
解释一下样例;
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
有两种翻转方案,都是翻转四块,
第一种: 0 1 1 0      第二种     0 0 0 0
         0 0 0 0                 1 0 0 1
         0 0 0 0                 1 0 0 1
         0 1 1 0                 0 0 0 0
以为第二种的字典序过小,所以输出第二种
思路:实在想不出有什么好的方法,只能暴力了。暴力优化一下就可以过,我们可以用二进制枚举第一行的所有可能翻转方案;然后根据第一行来确定第二行的翻转方案,一直往下。因为第一行确定了,那么接下来的行的翻转方案一定确定了,所以我们只需要最后判断最后一行,是否全部为0就行了,如果满足,就把这种方案记录下来;
假如一点的初始状态为0,那么他本身也许不需要翻转,但是它周围的点可能会执行翻转,我们可以把它本身的翻转次数和它周围(上下左右)点的翻转次数记录下来,对2取余,如果结果为1说明,它的状态为1,结果为0状态就为0,用这种方法,我们就可以根据上一行的状态,判断下一行的状态了。
我们用nm[][]数组代表所有块的初始状态
    用mn[][]数组表示每一种可能的翻转方案,把可行的方案进行比较
    用op[][]数组存最优解即可
 
 
解法和代码详细参照了https://www.cnblogs.com/helenawang/p/5538547.html,感谢大佬能让我这个菜鸟接受知识的洗礼。
具体代码和解析如下:
#include <string.h>
#include <stdio.h>
using namespace std;
int M, N;
#define inf 0x3f3f3f3f
int mn[20][20];//存储图
int nm[20][20];//存储翻转记录
int op[20][20], step;//最优解 , 翻转次数 
int d[5][2] = {0, 0, 0, -1, 0, 1, 1, 0, -1, 0};
int get(int x, int y) {
	int ans = 0;
	ans = mn[x][y];  //初始的翻转状态 
	for(int i = 0; i < 5; i++) {
		int dx = d[i][0] + x;
		int dy = d[i][1] + y;
		if(dx>=0 && dy>=0 && dx<M && dy<N) {
			ans += nm[dx][dy];
		}           //然后把它自身的翻转次数和旁边块的翻转次数记录下来 
	}
	ans %= 2;
	return ans;//如果ans为1,说明此时的状态为1,如果为0说明此时的状态为0 
}
int dfs() {
	int ans = 0;
	for(int i = 1; i < M; i++) {
		for(int j = 0; j < N; j++) {
			if(get(i-1, j)) 
			nm[i][j] = 1;//以为上方的块状态为1,所以此时需要翻转 
		}
	}
	//此时最后一行以上的部分全部翻转为了0 
	//下面是判断最后一行是否全翻转成了0 
	for(int i = 0; i < N; i++)
	if(get(M-1,i)) return inf;  //判断最底层是否都为0
	for(int i = 0 ; i < M; i++) {
		for(int j = 0; j < N; j++) {
			ans += nm[i][j];   //表示翻转的总次数 
		}
	}
	return ans; 
}
int main() {
	scanf("%d%d",&M,&N);
	step = inf;
	for(int i = 0; i < M; i++) {
		for(int j = 0; j < N; j++)
		scanf("%d",&mn[i][j]);
	}
	for(int i = 0; i < (1<<M); i++) { 
		memset(nm, 0, sizeof(nm));   
		for(int j = 1; j <= M; j++)
		nm[0][j-1] = i >> (M-j) & 1; //二进制枚举第一行的翻转情况
		int ans = dfs();
		if(step > ans) {
			step = ans;
			memcpy(op, nm, sizeof(nm));//把翻转状态传给最优解 
		}
	}
	if(step == inf) printf("IMPOSSIBLE\n");
	else
	for(int i = 0; i < M; i++) {
		for(int j = 0; j < N; j++) {
			printf("%d ",op[i][j]);
		}
		printf("\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值