逆向bfs搜索打表+康拓判重

HDU 1043八数码问题

  八数码,就是1~8加上一个空格的九宫格,这道题以及这个游戏的目标就是把九宫格还原到从左到右从上到下是1~8然后最后是空格。

  没了解康托展开之前,这道题怎么想都觉得很棘手,直接搜索的话也不知道怎么剪枝,而且判重也不可能开一个9维的数组,空间也不允许,所以先了解康托展开是无可厚非的第一步,这里就引用一下大佬的介绍,很简单很实用的关于全排列的一个东西

  康托展开和逆康托展开

  学会康托展开之后这道题就有很多解法了,很多是用A*的,不过这个我还没学会,只能弱弱的用万能的搜索来暴力过一切了,当然搜索的做法也有几个,直接暴力的正向搜索单组测试数据是可以的,单多组测试数据的话还是会超时,还有种双向bfs的做法,也就是把当前的数列和我们要的目标数列同时加入队列,然后两个相遇时就可以了。

  不过我个人觉得那两个写起来比较麻烦不好处理,所以我还是采用一个逆向的bfs来打表,为什么可以打表的,了解康托展开后我们可以知道,这题的全排列最多也就9!,那我们完全可以预处理一下,由目标状态去跑到其他状态,把每个状态的康托展开的值作为它的一个编号,由此打表,具体的如代码

 #include<cstdio>
#include<queue>
using namespace std;
const int dir[][]={{,},{,},{,-},{-,}};
const char leg[]={'l','u','r','d'};//因为是逆回去的,所以方向是反的
char s[];
int jc[]={},sm[];
struct Way{
char ans;
int f;
Way(){
f=-;
}
}w[];
struct Node{
int num[];
int kt,p;//康托值,x的位置
};
int kangtuo(int *a)//康托展开
{
int ans=;
for(int i=;i<;i++)
{
int k=;
for(int j=i+;j<;j++)
if(a[i]>a[j])
k++;
ans+=k*jc[-i];
}
return ans;
}
void bfs()
{
queue<Node> q;
Node b;
for(int i=;i<;i++)
b.num[i]=i;
b.kt=,b.p=;
q.push(b);
while(!q.empty())
{
Node e=q.front();
q.pop();
for(int i=;i<;i++)
{
int dx=e.p/+dir[i][];//二维比价好处理位置变化
int dy=e.p%+dir[i][];
if(dx>=&&dx<&&dy>=&&dy<)
{
b=e;b.p=dx*+dy;
int t=b.num[e.p];b.num[e.p]=b.num[b.p];b.num[b.p]=t;
b.kt=kangtuo(b.num);
if(w[b.kt].f==-)
{
w[b.kt].f=e.kt;
w[b.kt].ans=leg[i];
q.push(b);
}
}
}
}
}
int main()
{
for(int i=;i<=;i++)
jc[i]=jc[i-]*i;
bfs();
while(~scanf("%s",s))
{
for(int i=;i<;i++)
scanf("%s",s+i);
for(int i=;i<;i++)
if(s[i]>=''&&s[i]<='')
sm[i]=s[i]-'';
else
sm[i]=;
int kt=kangtuo(sm);
if(w[kt].f==-)//没遍历到这个状态
printf("unsolvable\n");
else
{
while(kt)//回溯输出答案
{
printf("%c",w[kt].ans);
kt=w[kt].f;
}
printf("\n");
}
}
return ;
}

代码千万条,自觉第一条,复制粘贴爽,打铁泪两行

  既然涉及到八数码,最后再补充一个结论,怎么直接判断八数码有没有解,这涉及到逆序数,目标状态1~8的逆序数是0,而上下左右的变换并不会改变逆序数的奇偶性,(这里说的逆序数是不计x的)

所以结论就是,序列的奇偶性要和目标状态一致。

上一篇:php strpos 用法实例教程


下一篇:AnalyticDB MySQL拥抱云原生,强力支撑双十一