POJ 1753 Flip Game 题解
题目解读
读到这个题目,首先要明确题目要求完成的任务。这道题的背景是翻转棋游戏。翻转棋游戏以4*4的棋盘为背景,有黑白两种颜色的棋子。现在题目中给出了在某一时刻棋盘上棋子的分布,要求每一回合选择棋盘上的某一个坐标,通过翻转规则作用棋盘发生变化。问最少经过几个回合能使得棋盘上的棋子变成全白或者全黑的状态。
显然,题目的重点在翻转规则:该游戏中,将选择修改的坐标棋子及其上下左右方向的棋子,共计五个进行翻转修改(如果上下左右有棋子的话则翻转,否则不翻转)。
解决算法
对题目进行抽象,比较容易想到的就是采用广度优先算法(BFS),通过逐层搜索,达到目标。现在先明确BFS中的几个关键变量。
关键变量 | 本题中的对象 |
---|---|
目标状态 | 棋盘上达到全白或者全黑 |
基本状态 | 棋盘上当前时刻黑白棋子的分布 |
状态转移 | 翻转规则:变换坐标以及上下左右共计五个棋子 |
第一个问题:如何表示棋盘状态?
在这里,我做题时思考了两种方法,一是直接利用string类字符串来记录整个棋盘,可以将棋盘存储为一个字符数组,然后通过函数转换成为字符串;二是观察到棋盘共计16个位置,刚好是一个short int的长度,因此可以考虑用二进制压缩的形式来表示棋盘。事实证明,这种二进制表示的形式有较快的运算速度,也不会出现超时的情况。
第二个问题:如何进行状态判重?
在广搜中,提高搜索效率,避免程序超时的很重要的方法就是进行剪枝操作,细化之后就是状态判重。之前在做广搜的题时,我会选择将其转换为整型数组,然后乘上10的幂次相加得到一个和来表示状态。这样做运算量大,而且有的时候计算结果会超出变量表示范围。这道题可以很容易分析得到棋盘的变化情况共有2^16次方共计65536种可能。一种思路是利用C++ STL中的map结构,向map中添加新的状态,通过map.find()和map.count()两种方法来检验判重;另一种思路是延续二进制的方法,通过开辟label[65536]的数组来标记是否已有该状态。不过第二种方法可能会浪费存储空间,但在寻找判重的时间上应该会略快于find()和count()方法。这里有一个小的trick就是,无论使用map还是label数组,都可以将它们对应的值用来表示到达这一状态所需的最少步骤,从而省去了另外开辟变量来记录的步骤。当然如果要是需要打印出变换路径,还是需要其他辅助变量的。
第三个问题:如何进行状态变换?
这里我以二进制压缩的方法作为说明对象。采用二进制表示的一大优点就在于状态的变换可以通过二进制运算来进行。计算机进行二进制运算是最拿手的。因此在这里我们只要确定要修改的棋子对应于哪些二进制位,然后通过取反对应位就可以完成。那么怎样做到取反对应位呢?首先,我们要确定哪些位要进行取反。这里举一个简单的例子:假设棋盘从左到右,从上到下依次编号为0,1,2,……,15。二进制数最右侧为No.0,最左侧为No.15,则我现在需要对No.4位做翻转,那么我需要先将1左移4位。因为1可以表示为 0000 0000 0000 0001b(这是二进制表示),通过“<<”左移运算符移位四次,能够将它变换为:0000 0000 0001 0000b,然后通过^取反操作即可完成对应位的取反。如果要同时进行多个位的变换,结合“|”运算即可。
遇到的问题
Problem 1:我在采用map+string的方法时,始终出现Time exceed的情况,个人认为是在调用相关string类函数的时候速度较慢,而且map中的判重函数find()和count()可能在这道题里面与string结合时效率不够高;(有很大可能是我用错了。。。)
Problem 2:在使用二进制压缩的方法时,对给出的测试样例能够通过,但提交时报出结果错误。通过比较代码等发现,问题出在变换调整的临界条件上。由于采用二进制表示,将4*4的棋盘拉成了在一行表示,我错误理解为不能取的棋子只会出现在编号小于0和编号大于15的情况。但一定要注意还原到棋盘的时候,对于index+1和index-1编号,极有可能加到下一行或者减到上一行去,这个地方被我疏忽掉了,导致结果错误,修改后的调整限制条件为:
收获
这是一个基本的BFS搜索题,但自己卡的时间还比较长,只能说练习的次数太少。在掌握BFS的模板后应该在很短的时间内就能AC。
代码
#include<iostream>
#include<queue>
using namespace std;
int label[65535];
queue<int> Q;
int Adjust(int middle,int index)
{
int next=middle;
unsigned short int ted=0;
ted = ted|(1<<index);
if(index+4<16){
ted=ted|(1<<(index+4));
}
if(index-4>=0){
ted=ted|(1<<(index-4));
}
if((index+1)%4!=0){
ted=ted|(1<<(index+1));
}
if((index%4!=0)&&(index-1>=0)){
ted=ted|(1<<(index-1));
}
next = next^ted;
return next;
}
int main()
{
int i,temp=0;
int middle,next,finals=-1;
char c;
for(i=0;i<65535;i++){
label[i]=-1;
}
for(i=0;i<16;i++){
cin>>c;
if(c=='b'){
temp = temp|(1<<i);
}
}
label[temp]=0;
Q.push(temp);
if(temp==65535||temp==0){
cout<<0<<endl;
}
else{
while(!Q.empty()){
middle=Q.front();
Q.pop();
for(i=0;i<16;i++){
next=Adjust(middle,i);
if(next==65535||next==0){
label[next]=label[middle]+1;
finals=label[next];
break;
}
if(label[next]!=-1){
continue;
}
else{
label[next]=label[middle]+1;
Q.push(next);
}
}
if(finals!=-1){
break;
}
}
if(finals!=-1){
cout<<finals<<endl;
}
else{
cout<<"Impossible"<<endl;
}
}
return 0;
}