题意:给定一个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;
}