前言
就是想练习一下双向广搜,然后就加强了这一题的数据。
我也不知道其他方法能不能过(实际上就是太懒了,不想写其他方法。。。)
好啦,回归正题。
双向广搜
什么是双向广搜?
所谓双向搜索指的是搜索沿两个方向同时进行:正向搜索:从初始结点向目标结点方向搜索;逆向搜索:从目标结点向初始结点方向搜索;当两个方向的搜索生成同一子结点时终止此搜索过程。
画个图就是这样(图片来自网络)
怎么实现双向广搜?
1.需要两个队列 (废话)分别简记为 q s qs qs q e qe qe,两个标记数组 vs ve (分别用来记录两边搜索达到的状态)。
2.确定初始状态( s t a r t start start ) 与目标状态( e n d end end ) 。
3.开始搜索
<1> 首先将 s t a r t start start 放入 q s qs qs 中, e n d end end 放入 q e qe qe 中。
<2> 若两队列有一个不为空进行一下循环
若 q s qs qs 不为空,取出队头元素,若队头状态 v e ve ve 中已经存在,证明已经找到,退出。
否则,进行扩展,将可达的状态放入 q s qs qs 队尾中,同时在 v s vs vs 数组中标记
若 q e qe qe 不为空,取出队头元素,若队头状态 v s vs vs 中已经存在,证明已经找到,退出。
否则,进行扩展,将可达的状态放入 q e qe qe 队尾中,同时在 v e ve ve 数组中标记
<3> 到达此处说明无解,搜索结束。
划重点
1. 双向广搜适用于有确定的初始状态和目标状态的搜索。
2. 初始状态到达目标状态最好有解,否则双向广搜 甚至 \color{red}\huge{\textsf{甚至}} 甚至 不如单向广搜。
3. q s qs qs 与 q e qe qe 扩展判重时,一定要分别判重 (代码中有解释)。
综上所述,用双向广搜来写八数码,是比较合适的。(不存在的情况可以用逆序对的奇偶性排除掉)。
代码如下
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#define MAXN 362887
using namespace std;
int fact[11]={0,1,2,6,24,120,720,5040,40320,362880,3628800};
struct node{
int k[10],step;
int t;
}st,en;
node qs[MAXN],qe[MAXN]; //两个队列
int ve[MAXN],vs[MAXN]; //状态数组,记录到达每种状态所需的最小步数
inline void read(int &x){ //快读,没什么说的,习惯而已
char ch=getchar();x=0;int fl=1;
while(ch<'0'||ch>'9'){if(ch=='-') fl=-fl;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
x*=fl;
}
inline int KT(node x){ //康拓展开,将每种状态转换成 1~10! 之间的数字。类似于状态压缩
int res=0;
for(int i=1;i<=9;++i){
int tmp=0;
for(int j=i+1;j<=9;++j){
if(x.k[j]<x.k[i]) ++tmp;
}
res+=(tmp*fact[9-i]);
}
return res+1;
}
inline bool isOK(int ind,int opt){ //判断0往opt这个方向走合不合法
switch(opt){
case 1:if(ind%3==1){return 0;}else{return 1;} break;
case 2:if(ind%3==0){return 0;}else{return 1;} break;
case 3:if(ind<=3){return 0;}else{return 1;} break;
case 4:if(ind>6){return 0;}else{return 1;} break;
}
return 0;
}
inline node change(node x,int opt){ //如果合法,则进行扩展,并返回新的状态
int ind=x.k[0];
switch(opt){
case 1:swap(x.k[ind],x.k[ind-1]);x.k[0]=ind-1;return x;
case 2:swap(x.k[ind],x.k[ind+1]);x.k[0]=ind+1;return x;
case 3:swap(x.k[ind],x.k[ind-3]);x.k[0]=ind-3;return x;
case 4:swap(x.k[ind],x.k[ind+3]);x.k[0]=ind+3;return x;
}
return x;
}
inline void dbfs(){ //双向广搜
memset(ve,0,sizeof(ve)); //标记数组清零
memset(vs,0,sizeof(vs));
int ls=0,rs=1,le=0,re=1; //队列清空
qs[1]=st,qe[1]=en; //推入初状态与末状态
vs[st.t]=1,ve[en.t]=1; //进行标记
while(ls<=rs||le<=re){
if(ls<=rs){
++ls; //循环队列的操作,下同
if(ls>362880) ls=1;
node x=qs[ls];
if(ve[x.t]!=0){ //如果从末状态已经搜索到了,意味着找到了解,退出
printf("%d\n",ve[x.t]+vs[x.t]-2);
return;
}
for(int i=1;i<=4;++i){ //扩展新的状态
if(isOK(x.k[0],i)){
node y=change(x,i);
y.t=KT(y);
if(vs[y.t]||x.step>2500) continue; //若从初始状态扩展时已经有了这种状态
y.step=x.step+1; //跳过
vs[y.t]=y.step;
++rs;
if(rs>362880) rs=1;
qs[rs]=y;
}
}
}
if(le<=re){
++le;
if(le>362880) le=1;
node x=qe[le];
if(vs[x.t]!=0){ //如果从末状态已经搜索到了,意味着找到了解,退出
printf("%d\n",ve[x.t]+vs[x.t]-2);
return;
}
for(int i=1;i<=4;++i){ //扩展新的状态
if(isOK(x.k[0],i)){
node y=change(x,i);
y.t=KT(y);
if(ve[y.t]||x.step>2500) continue; //若从末状态扩展时已经有了这种状态
y.step=x.step+1; //跳过
ve[y.t]=y.step;
++re;
if(re>362880) re=1;
qe[re]=y;
}
}
}
}
printf("-1\n");
}
inline bool jud(node x){ //暴力求逆序对,判断是否可以到达
int res=0;
for(int i=1;i<=9;++i){
if(x.k[i]==0) continue;
for(int j=i+1;j<=9;++j){
if(x.k[j]==0) continue;
if(x.k[i]>x.k[j]) ++res;
}
}
return res%2;
}
inline void init(){
while(~scanf("%d",&st.k[1])){
if(st.k[1]==0) st.k[0]=1;
for(int i=2;i<=9;++i){
read(st.k[i]);
if(st.k[i]==0) st.k[0]=i;
}
for(int i=1;i<=9;++i){
read(en.k[i]);
if(en.k[i]==0) en.k[0]=i;
}
if(jud(st)!=jud(en)){
printf("-1\n");
continue;
}
st.step=1,en.step=1;
st.t=KT(st),en.t=KT(en);
dbfs();
}
}
int main(){
init();
return 0;
}