[kuangbin带你飞]专题一 简单搜索 D - Fliptile POJ - 3279

题目描述

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

本题的大意是有一个棋盘,棋盘上面的格子是可以翻转的,每个格子的一面是白色一面是黑色。每次翻转一个格子,都会带动上下左右的格子同时翻转。给定一个棋盘,1代表黑色,0代表白色。要求输出一种翻转方案,可以用最少的翻转次数让棋盘的格子都是白色。如果存在多个解,按照逆词典顺序输出第一个解。

解题思路

这道题虽然被归到了搜索里,但其实是一个枚举的问题,并没有用到搜索算法(或许枚举也算一种搜索?)。当然这里肯定不能枚举所有情况,复杂度太高。由题目可知,翻转格子的时候,周围的格子也会被翻转,因此我们要从邻居影响的角度来考虑。我们从上往下的逐行来解决这个问题,每次扫描一行,对于一行中的每一个格子,他是否需要翻转仅由该格子的上一行相同位置的格子的颜色决定的。举个例子,(2,3)位置的格子是否需要翻转,仅由(1,3)位置格子的颜色决定。如果(1,3)位置的格子颜色是黑色,那么(2.3)就要翻转,如果(1,3)位置的格子颜色是白色,就不需要翻转。那么每个格子的颜色是如何确定的呢?首先,格子会有一个初始的颜色,格子会自己翻转,或者受到周围邻居的影响也会翻转,我们记录格子翻转的次数。格子初始颜色是黑色是1,白色是0,那么格子的颜色就用(初始颜色+翻转次数)%2来判断,当取余的结果为0,说明此时格子的颜色是白色,当结果不为0,那么格子的颜色就是黑色。还有一点需要注意的是,每个格子我们最多只会翻转一次,因为翻转多了没有意义,翻转偶数次数的结果和不翻转的结果一样,翻转奇数次数的结果和翻转一次的结果是一样的。
前文说了,每一行的格子的是否翻转由前一行相同位置的格子颜色决定的,那么第一行的格子翻转情况怎么办?这里就要用到枚举了,我们对第一行格子的所有翻转情况进行枚举,当每一行有n个格子,那么我们就枚举 2 n 2^{n} 2n种翻转情况,对于每一种枚举的情况,从第二行开始,都按照前面所说的方法逐行扫描。当最后一行扫描结束,对最后一行所有格子的颜色进行判断,当最后一行的格子都是白色,那么说明问题被解决,所有格子都是白色的了。如果最后一行有黑色的格子,说明问题没有解决,继续枚举其他的情况。如果枚举了所有情况问题都没有被解决,那么输出IMPOSSIBLE。
本文需要在次数相同的情况下按照逆词典序输出结果,那么我们用字典序枚举,如果有相同的情况,最后输出的肯定就是逆字典序的第一个了。

AC 代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
int map[16][16];
int choose[16][16];
int flip_num[16][16];
int n,m;
bool is_white(int i,int j)
{

	if((map[i][j]+flip_num[i][j])%2==0) return true;
	else return false;
}
int solve()
{
	int num = 0;
	int i,j;
	
	memset(flip_num, 0, sizeof(flip_num));	
	for(i = 0;i<n;i++)
	{
		if(choose[0][i]==1)
		{
			num+=1;
			flip_num[0][i]+=1;
			flip_num[1][i]+=1;
			if(i-1>=0) flip_num[0][i-1]+=1;
			if(i+1<n) flip_num[0][i+1]+=1;
		 } 
	}
	for(i = 1;i<m;i++)
	{
		for(j = 0;j<n;j++)
		{
			
			if(!is_white(i-1,j))
			{
				flip_num[i][j]+=1;
				flip_num[i-1][j]+=1;
				flip_num[i+1][j]+=1;
				if(j-1>=0) flip_num[i][j-1]+=1;
				if(j+1<n) flip_num[i][j+1]+=1;
				choose[i][j] = 1;
				num+=1;
			}
		}
	}
	for(i = 0;i<n;i++)
	{
		if(!is_white(m-1,i))
		{
			num = -1;
			return num;
		}
	}
	
	return num;
	
}
int main()
{
	
	cin>>m>>n;
	int i,j;
	for(i = 0;i<m;i++)
	{
		for(j = 0;j<n;j++) cin>>map[i][j];
	}
	int goal[16][16];
	int Min = -1;
	int temp;
	for(i = 0;i<(1<<n);i++)
	{
		memset(choose, 0, sizeof(choose));	
		for(j = 0;j<n;j++) choose[0][j] = (i >> j) & 1;
		
		temp = solve();

		if(temp>=0&&(temp<Min||Min<0))
		{
			Min = temp;
			memcpy(goal, choose,sizeof(choose));

		}
		
	}
	if(Min==-1) printf("IMPOSSIBLE\n");
	else
	{
		for(i = 0;i<m;i++)
	   {
	   	  cout<<goal[i][0];
		  for(j = 1;j<n;j++) cout<<" "<<goal[i][j];
		  cout<<endl;
		  
	   }
	}
	
 } 

踩的坑

这里写代码时候,if(temp>=0&&(temp<Min||Min<0)) 这里判读返回的次数和现有最小次数时候,temp>=0这里忘记加等于号了,导致WA。这里没有等于号的话输入是全0 的情况会输出“IMPOSSIBLE”。

学到的技巧

这份代码是参考https://blog.csdn.net/weixin_43741224/article/details/86495614写的。学习到了枚举 2 n 2^{n} 2n的方法,C++中使用位移符>>可以很方便的得到所有的枚举情况。

for(i = 0;i<(1<<n);i++)
	{
		memset(choose, 0, sizeof(choose));	
		for(j = 0;j<n;j++) choose[0][j] = (i >> j) & 1;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值