Fliptile POJ - 3279(状态压缩, 搜索)

Fliptile

题目链接:POJ - 3279
题意:M*N的格子, 每个小格子边长为单位长度1, 0表示格子为白色, 1表示黑色, 每次翻转一个格子可以使它本身和四周的四个格子由1变0, 或由0变1;最后要使全部格子都是白色, 即都为0;
每个格子最多翻转一次, 若翻转两次, 便又回到了之前的状态, 所以应该有2^(n*m)种操作方式;m和n的范围是1~15, 所以如果直接爆搜的话,,,2^225, 会跑到下一次宇宙大爆炸???
换个思想, 对于第一行, 有2^n种操作, 处理完第一行后, 看下一行, 如果此行的上一行的对应位置是1, 那么一定要把此位置翻转, 因为此时上一行已经处理完,只有该位置翻转才能改变上一行的对应位置, 依次处理, 直到处理完最后一行结束;
在判断最后一行是否已经全为0(此时上边的每一行都已处理完, 且不会有1), 若是, 那么这是一种可行操作, 记录下来, 最后输出操作次数最小的;

#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int G[20][20], ans, m, n, f[20][20], p[20][20], q[20][20];
void flip(int i, int j){//翻转i,j位置,本身和四周方格同时翻转;
	f[i][j]=!f[i][j];
	if(i+1<=m)
		f[i+1][j]=!f[i+1][j];
	if(i-1>0)
		f[i-1][j]=!f[i-1][j];
	if(j+1<=n)
		f[i][j+1]=!f[i][j+1];
	if(j-1>0)
		f[i][j-1]=!f[i][j-1];
}
int Count(int k){//第一行操作状态为k时, 已经操作的次数;
	int cnt=0;
	while(k){
		cnt+=k&1;
		k>>=1;
	}
	return cnt;
}
int main(){
	scanf("%d%d", &m, &n);
	for(int i=1; i<=m; i++){
		for(int j=1; j<=n; j++){
			scanf("%d", &G[i][j]);
		}
	}
	int res=0, w=(1<<n);
	ans=INF;
	for(int k=0; k<w; k++){
		res=Count(k);
		memset(q, 0, sizeof(q));
		for(int i=1; i<=m; i++){
			for(int j=1; j<=n; j++){
				f[i][j]=G[i][j];
			}
		}
		for(int j=1; j<=n; j++){
			if((1<<(j-1))&k){//对应位置是否翻转;
				flip(1, j);
				q[1][j]=1;
			}
		}
		for(int i=2; i<=m; i++){
			for(int j=1; j<=n; j++){
				if(f[i-1][j]){
					flip(i, j);
					q[i][j]=1;
					res++;
				}
			}
		}
		int flag=0;
		for(int j=1; j<=n; j++){
			if(f[m][j]){
				flag=1;
				break;
			} 
		}
		if(flag) continue;
		if(res<ans){
			ans=res;
			for(int i=1; i<=m; i++){
				for(int j=1; j<=n; j++){
					p[i][j]=q[i][j];
				}
			}
		}
	}
	if(ans>=INF) printf("IMPOSSIBLE\n");
	else 
	for(int i=1; i<=m; i++){
		for(int j=1; j<=n; j++){
			printf("%d%c", p[i][j], j==n?'\n':' ');
		}
	}
	return 0;
}



猜你喜欢

转载自blog.csdn.net/sirius_han/article/details/80226089