ZJU2050 Flip Game - 双向广搜+状态压缩

来源:互联网 发布:真人cs好玩吗 知乎 编辑:程序博客网 时间:2024/05/20 06:52

题目大意:

一个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;
}