题目大意:
一个4×4的棋盘,每个格子放着一个棋子。棋子一面是白色,一面是黑色。一次操作可以将某一个格子以及上下左右共5个格子的棋子都翻过来,即白色变黑色,黑色变白色。现在给出一种棋盘状态,问最少需要几次操作可以将棋盘全部变为同种颜色。
分析:
很明显是一个搜索题目。
直接按照题目描述做法是,给出一种状态,从这个状态出发去寻找全部白色或黑色的状态。
由于是从很多不同的棋盘去搜索全白或全黑的棋盘,一个很重要的优化就是,直接从白色或黑色棋盘出发把所有可以到达的状态全部搜出来。对于输入的数据可以直接输出答案。这样可以节省很多搜索时间,避免了重复工作。
这个搜索过程可以使用双向广搜。(其实因为两边搜索是对称的,只需要搜索一边即可。因为好久没写双向广搜了,就拿这个题练练手。)
状态压缩:
由于棋盘格子数量较少,只有16个,每个格子只有01两种状态,于是我们可以用一个int变量表示一种棋盘状态。
很简单,从棋盘坐上到右下,16个格子依次对应int数字的二进制的前16位。最多也只有2^16=65536种状态。
双向广搜:
如何判重:判重可以用一个一维数组e[x]表示到达压缩后的状态x,最少需要的操作次数。
如何扩展状态:对于一个状态x,将x解压得到棋盘数组a[][]。尝试对这个棋盘的16格子操作可以得到16个新的状态y,判断y是否在e[]数组中存在,或是之前的操作数较多,决定是否将新状态y加入搜索队列。
如何替换搜索方向:搜索队列用一个二维数组保存b[0][],b[1][]分别是搜索的两个方向的队列。一个方向扩展个节点后就交换到另一个方向;若当前方向没有状态可以扩展则直接交换方向。
--------------------------------------------------------------------------
/*
ZJU2050 Flip Game
*/
#include <stdio.h>
#include <string.h>
#define clr(a) memset(a,0,sizeof(a))
#define N 4
#define M 65536
//vars
int b[2][M];
int p[2],q[2];
int e[M];
int dir[][2]={{0,0},{1,0},{0,1},{-1,0},{0,-1}};
//functions
void x2a(int x,int a[][N]){ //解压状态函数 从第位往高位存放
int i,j,k;
for(i=k=0;i<N;i++)
for(j=0;j<N;j++,k++){
a[i][j]=(x>>k)&1;
}
}
int a2x(int a[][N]){
int i,j,k,x=0;
for(k=i=0;i<N;i++)
for(j=0;j<N;j++,k++){
x|=a[i][j]<<k;
}
return x;
}
void alter(int a[][N],int x0,int y0){
int x,y,k;
for(k=0;k<5;k++){
x=x0+dir[k][0];
y=y0+dir[k][1];
if(x<0||y<0||x>=N||y>=N) continue;
a[x][y]=!a[x][y];
}
}
void printa(int a[][N]){
int i,j;
for(i=0;i<N;i++){
for(j=0;j<N;j++) printf("%d ",a[i][j]);
puts("");
}
}
void prework(){
int i,j,k;
int x,y;
int a[N][N];
//初始化广搜
b[0][0]=0; //all white
b[1][0]=0xFFFF; //all black
p[0]=p[1]=0; q[0]=q[1]=1;
clr(e); e[0]=e[0xFFFF]=1;
/********************
* 双向广搜
********************/
k=0;
while(p[0]<q[0] || p[1]<q[1]){
if(p[k]>=q[k]){ k=!k; continue; }
x=b[k][p[k]];
//状态解压
x2a(x,a);
//扩展状态
for(i=0;i<N;i++){
for(j=0;j<N;j++){
alter(a,i,j);
y=a2x(a); //状态压缩
alter(a,i,j);
if(!e[y]||e[y]>e[x]+1){ //添加节点
b[k][q[k]++]=y;
e[y]=e[x]+1;
}
}
}
p[k]++;
k=!k; //交换搜索方向
}
}
int main()
{
int i,j,k,m,n,T;
char str[N+N];
int x,a[N][N];
prework();
scanf("%d",&T);
while(T--){
for(i=0;i<N;i++){
scanf("%s",str);
for(j=0;j<N;j++){
if(str[j]=='w') a[i][j]=0;
else a[i][j]=1;
}
}
x=a2x(a);
if(!e[x]) puts("Impossible");
else printf("%d/n",e[x]-1);
if(T) puts("");
}
return 0;
}