题目大意:有一个4*4的方格,每个方格中放一粒棋子,这个棋子一面是白色,一面是黑色。游戏规则为每次任选16颗中的一颗,把选中的这颗以及它四周的棋子一并反过来,当所有的棋子都是同一个颜色朝上时,游戏就完成了。现在给定一个初始状态,要求输出能够完成游戏所需翻转的最小次数,如果初始状态已经达到要求输出0。如果不可能完成游戏,输出Impossible。
主要思想:
1.如果用一个4*4的数组存储每一种状态,不但存储空间很大,而且在穷举状态时也不方便记录。因为每一颗棋子都只有两种状态,所以可以用二进制0和1表示每一个棋子的状态,则棋盘的状态就可以用一个16位的整数唯一标识。而翻转的操作也可以通过通过位操作来完成。显然当棋盘状态id为0(全白)或65535(全黑)时,游戏结束。
2.对于棋盘的每一个状态,都有十六种操作,首先要判断这十六种操作之后是否有完成的情况,如果没有,则再对这十六种操作的结果分别再进行上述操作,显然这里就要用到队列来存储了。而且在翻转的过程中有可能会回到之前的某种状态,而这种重复的状态是不应该再次入队的,所以维护Vis[i]数组来判断id==i的状态之前是否已经出现过,如果不是才将其入队。如果游戏无法完成,状态必定会形成循环,由于重复状态不会再次入队,所以最后的队列一定会是空队列。
3.由于0^1=1,1^1=0,所以翻转的操作可以通过异或操作来完成,而翻转的位置可以通过移位来确定
#include<stdio.h> #include<stdlib.h> #include<iostream> using namespace std; #define Max 65535 int queue[Max*2]; //BFS算法中的队列 int step[Max]; //记录是翻第几次 int vis[Max]; //记录是否翻过这种状态 int tab=0; //记录是否能翻成功 void bfs(int s){ int head=0,rear=0; step[s]=0; if(s==0||s==Max){ printf("%d\n",0); tab=1; return; } queue[rear++]=s; vis[s]=1; while(head<rear){ int temp=queue[head++]; int t=temp; for(int i=0;i<16;i++){ temp=t; //为下一次翻棋子保护状态 temp^=1<<(15-i); int tm; int label=15-i; tm=label+4; //处理上边的棋子 if(tm!=19&&tm!=18&&tm!=17&&tm!=16) temp^=1<<tm; tm=label-4; //处理下边的棋子 if(tm!=-1&&tm!=-2&&tm!=-3&&tm!=-4) temp^=1<<tm; tm=label+1; //处理左边的棋子 if(tm!=16&&tm!=12&&tm!=8&&tm!=4) temp^=1<<tm; tm=label-1; //处理右边的棋子 if(tm!=11&&tm!=7&&tm!=3&&tm!=-1) temp^=1<<tm; if(temp==0||temp==65535){ printf("%d\n",step[t]+1); tab=1; return; } if(vis[temp]==0){ queue[rear++]=temp; vis[temp]=1; step[temp]=step[t]+1; } } } } int main(){ int i,j; char color; int id=0; for(i=0;i<4;i++) for(j=0;j<4;j++) { cin>>color; id<<=1; if(color=='b') id+=1; //计算当前状态 } bfs(id); if(tab==0) printf("Impossible"); return 0; }