Poj 3279 Fliptile(反转/开关问题)

题意:给定一个N*M的矩形,每个点上的值为0或1,0表示该点为白色棋子,1表示该点为黑色棋子。当改变一个棋子的颜色时,会连同它上下左右四个棋子共同变色。问:最少变换几次能将所有棋子都变为黑色;若有多组最小值相等的解,输出字典序最小的那个。


思路:

对于这样的变换来说,有两点可以总结:

一、反转顺序的交换对结果没有影响;

二、对一个点进行两次反转后结果不变。

好好的矩阵说着说着要求字典序最小……于是先枚举第一行的变化情况:对于第一行,确定了字典序大小,并且当第一行确定了以后之后每一行的解可以确定:

思考从第二行开始的每一行:对某个点来说,如果它上方的点是黑色的,则它必须被反转(因为对于它上方的点,已经无法受到除了它下面的其他点的影响了,因为其他三个方向的点均已被考虑过),因此判断该点上方的点即可知道该点需不需要反转。在这个策略的前提下,第1到第n-1行都可以全部变为白色,唯一要考虑的就是最后一行第n行是不是白色。因为上方的已经调整完毕,故如果这一行无法再进行颜色改变,若有非白色,则这种方法不可行。



AC代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n , m , map[20][20] , flip[20][20] , save[20][20] ;
int dir[5][2] = {0,0,-1,0,1,0,0,1,0,-1};

int get ( int x , int y ){
	int color = map[x][y] ;
	for ( int i = 0 ; i < 5 ; i ++ ){				//判断这一位的原始颜色并求出总共经过几次颜色变换 
		int xx = x + dir[i][0] , yy = y + dir[i][1] ;
		if ( xx >= 0 && xx < n && yy >= 0 && yy < m ){
			color += flip[xx][yy] ;
		}
	}
	return color&1 ;
}

int calc (){
	for ( int i = 1 ; i < n ; i ++ ){
		for ( int j = 0 ; j < m ; j ++ ){
			if ( get(i-1,j) ){
				flip[i][j] = 1 ;
			}
		}
	}
	
	for ( int i = 0 ; i < m ; i ++ ){				//判断最后一行是否满足全白 
		if ( get(n-1,i) ){
			return -1 ;
		}
	}
	
	int res = 0 ;									//统计在这种情况下经过几次反转 
	for ( int i = 0 ; i < n ; i ++ ){
		for ( int j = 0 ; j < m ; j ++ ){
			res += flip[i][j] ;
		}
	}
	
	return res ;
	
}

int main(void){
	
	scanf ("%d%d",&n,&m );
	for ( int i = 0 ; i < n ; i ++ ){
		for ( int j = 0 ; j < m ; j ++ ){
			scanf ("%d",&map[i][j] );
		}
	}
	
	int k = 1 << m ;
	int res = -1 ;
	
	for ( int i = 0 ; i < k ; i ++ ){				//模拟m列每一列的二进制 
		memset ( flip , 0 , sizeof(flip) );
		for ( int j = 0 ; j < m ; j ++ ){
			flip[0][m-j-1] = ( i >> j ) & 1 ;
		}
		int num = calc() ;
		if ( num >= 0 && ( res < 0 || res > num ) ){
			res = num ;
			for ( int p = 0 ; p < n ; p ++ ){
				for ( int q = 0 ; q < m ; q ++ ){
					save[p][q] = flip[p][q] ;
				}
			}
		}
	}
	
	if ( res < 0 ){
		printf ("IMPOSSIBLE\n");
	} else {
		for ( int p = 0 ; p < n ; p ++ ){
			for ( int q = 0 ; q < m ; q ++ ){
				printf ("%d%c",save[p][q],( q == m - 1 ) ? '\n' : ' ' );
			}
		}
	}
	
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值