[PKU] 1753 Flip Game [状态压缩,DFS/BFS,枚举]
题目来源:PKU 1753 [Northeastern Europe 2000]
题目大意:有一个4*4的棋盘,每个位置可放黑棋或白棋,给你一个初始摆放状态,经过一定操作,使得最终棋牌为全黑或全白。规则如下:选定任意一枚棋子,连同它四周的棋子(若有的话),都变为其相反的颜色(即黑变白,白变黑),此记为一次有效操作。问:最少经过这样几次操作能够得到最终结果(即全黑或全白)?
简单分析:根据输入要求,b代表黑棋(black),w代表白棋(white)。因为总共才16个位置,且只有黑白两种表示,此时,可对每一次状态进行二进制压缩(其中b代表1,w代表0),例如:
bwwb
bbwb
bwwb
bwww
即可表示为1001 1101 1001 1000,其十进制值为40344。同时,计算可知根据黑白棋的摆放情况,总共有2^16种不同的状态。每一次经过有效的操作后,状态都会发生改变,此时,可借助二进制位运运算实现状态的改变,即对原有状态(相应的十进制表示)进行异或操作,以此来改变其对应二进制数的相关位置的值(1变0,0变1)。
例如:
先假设前一个状态为:
wwww
wwww
wwww
wwww
即二进制表示为0000 0000 0000 0000,十进制对应为0。若此时选定左上角第一个棋子进行操作,根据规则,它右边和下边的也要同时进行变换(因为其左边和上边为空,不做考虑),之后,相应的状态用二进制表示,应变为:1100 1000 0000 0000,十进制值为51200。这个过程相当于对十进制数51200进行对十进制数0的异或操作,即next=0^(51200),而51200这个数则可以根据对十进制数1进行相应的左移操作得到。同时,我们知道,棋牌总共有16个位置,也就是说相应的不同的操作也有16种,即有16个不同的数经过异或操作用来改变前一个状态的值。那么,就先将这16个数枚出来吧,代码如下:
void init() { int p[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; int i,j,k,temp,x,y; for(i=0;i<4;++i) for(j=0;j<4;++j) { temp=0; temp^=(1<<(i*4+j)); for(k=0;k<4;++k) { x=i+p[k][1]; y=j+p[k][0]; if(x<0||y<0||x>3||y>3) continue; temp^=(1<<(x*4+y)); } printf("%d,",temp); } }
之后,问题就变得简单了,只要依次遍历所有的状态,即可得出相应的结果,当然,要合理的选择遍历的方法。根据下面的代码,像常见的DFS跟BFS都可以AC,但是,我实现的DFS要遍历完所有的状态才能得出结果,并且采用递归版本,所以效率不高,AC时的时间为16MS。BFS为0MS,因为遍历的时候总是在最终结果的到达与未到达之间,即到达最终结果的时候,此时改变的次数也最少。
完整的AC代码如下:
#include<iostream> using namespace std; int t[]={19,39,78,140,305,626,1252,2248,4880,10016,20032,35968,12544,29184,58368,51200}; int MIN; //void DFS(int state,int num,int deep) //{ // if(deep>15) { // if(state==0||state==65535) { // if(num<MIN) MIN=num; // } // return; // } // DFS(state^t[deep],num+1,deep+1); // DFS(state,num,deep+1); //} // const int SIZE=65535; int BFS(int state) { int visited[SIZE],d[SIZE],u,v,i; int Qu[SIZE],rear,front; memset(visited,0,sizeof(visited)); visited[state]=1; d[state]=0; rear=front=0; Qu[++rear]=state; while(rear!=front) { u=Qu[++front]; for(i=0;i<16;++i) { v=u^t[i]; if(v==0 || v==65535) return d[u]+1; if(visited[v]==0) { visited[v]=1; d[v]=d[u]+1; Qu[++rear]=v; } } visited[u]=-1; } return -1; } int main() { // init(); char ch[5][5]; int i,j,start; while(scanf("%s",ch[0])!=EOF){ start=0;MIN=0xfffffff; for(i=1;i<4;++i) scanf("%s",ch[i]); for(i=0;i<4;++i) for(j=0;j<4;++j) if(ch[i][j]=='b') start^=(1<<((3-i)*4+(3-j))); if(start==0||start==65535) printf("0\n"); else { // DFS(start,0,0); // MIN==0xfffffff? printf("Impossible\n"):printf("%d\n",MIN); int result=BFS(start); result==-1?printf("Impossible\n"):printf("%d\n",result); } } return 0; }
最后附上(据说是官方的)测试数据:
bwbw wwww bbwb bwwb Impossible bwwb bbwb bwwb bwww 4 wwww wwww wwww wwww 0 bbbb bbbb bbbb bbbb 0 bbbb bwbb bbbb bbbb Impossible bwbb bwbb bwbb bbbb Impossible bwbb wwwb bwbb bbbb 1 wwww wwwb wwbb wwwb 1 wwww wwww wwwb wwbb 1 wbwb bwbw wbwb bwbw Impossible bbbb bwwb bwwb bbbb 4 bwwb wbbw wbbw bwwb 4 bbww bbww wwbb wwbb Impossible bbwb bbbw wwbb wwwb Impossible wwwb wwbw wbww wwbw Impossible bbbb wwww wwbb wbbb Impossible bwwb wbwb wbbb wbbb 4 bwbb bwbb bwbw bbbw 5 wbwb bbbb bbww wbbb 6 bbwb bbbb wbwb bbbb 5