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

HDU 1043八数码问题

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

没了解康托展开之前,这道题怎么想都觉得很棘手,直接搜索的话也不知道怎么剪枝,而且判重也不可能开一个9维的数组,空间也不允许

所以先了解康托展开是无可厚非的第一步,这里就引用一下大佬的介绍,很简单很实用的关于全排列的一个东西康托展开和逆康托展开

学会康托展开之后这道题就有很多解法了,很多是用A*的,不过这个我还没学会,只能弱弱的用万能的搜索来暴力过一切了

当然搜索的做法也有几个,直接暴力的正向搜索单组测试数据是可以的,单多组测试数据的话还是会超时,

还有种双向bfs的做法,也就是把当前的数列和我们要的目标数列同时加入队列,然后两个相遇时就可以了。

不过我个人觉得那两个写起来比较麻烦不好处理,所以我还是采用一个逆向的bfs来打表,为什么可以打表的,了解康托展开后我们可以知道,这题的全排列最多也就9!,

那我们完全可以预处理一下,由目标状态去跑到其他状态,把每个状态的康托展开的值作为它的一个编号,由此打表,具体的如代码

 1 #include<cstdio>
 2 #include<queue>
 3 using namespace std;
 4 const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
 5 const char leg[4]={'l','u','r','d'};//这里存储的方向是相反的,因为我们是逆回去 
 6 char s[10];
 7 int jc[10]={1},sm[10];
 8 struct Way{
 9     char ans;//保存这个节点到上一个节点的答案 
10     int f;//父节点 
11     Way(){
12         f=-1;
13     }
14 }w[371108];
15 struct Node{
16     int num[10];
17     int kt,p;//康托值,x所在的位置 
18 };
19 int kangtuo(int *a)//康托展开 
20 {
21     int ans=0;
22     for(int i=0;i<9;i++)
23     {
24         int k=0;
25         for(int j=i+1;j<9;j++)
26             if(a[i]>a[j])
27                 k++;
28         ans+=k*jc[8-i];
29     }
30     return ans;
31 }
32 void bfs()
33 {
34     queue<Node> q;
35     Node b;
36     for(int i=0;i<9;i++)
37         b.num[i]=i;//目标状态1~9(x用9代替) 
38     b.kt=0,b.p=8;
39     q.push(b);
40     while(!q.empty())
41     {
42         Node e=q.front();
43         q.pop();
44         for(int i=0;i<4;i++)
45         {
46             int dx=e.p/3+dir[i][0];//一维转二维处理x的位置变化 
47             int dy=e.p%3+dir[i][1];
48             if(dx>=0&&dx<3&&dy>=0&&dy<3)
49             {
50                 b=e;b.p=dx*3+dy;
51                 int t=b.num[e.p];b.num[e.p]=b.num[b.p];b.num[b.p]=t;
52                 b.kt=kangtuo(b.num);
53                 if(w[b.kt].f==-1)//这个状态还未被遍历到 
54                 {
55                     w[b.kt].f=e.kt;//记录父节点 
56                     w[b.kt].ans=leg[i];
57                     q.push(b); 
58                 }
59             }
60         }
61     }
62 }
63 int main()
64 {
65     for(int i=1;i<=10;i++)
66         jc[i]=jc[i-1]*i;
67     bfs();
68     while(~scanf("%s",s))
69     {
70         for(int i=1;i<9;i++)
71             scanf("%s",s+i); 
72         for(int i=0;i<9;i++)
73             if(s[i]>='1'&&s[i]<='8')
74                 sm[i]=s[i]-'0';
75             else
76                 sm[i]=9;
77         int kt=kangtuo(sm);
78         if(w[kt].f==-1)//没被遍历到的结果 
79             printf("unsolvable\n");
80         else
81         {
82             while(kt)
83             {
84                 printf("%c",w[kt].ans);
85                 kt=w[kt].f;
86             }//返回去输出答案 
87             printf("\n");
88         }
89     }
90     return 0;
91 } 
点一下看一年,代码不花一分钱

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

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

猜你喜欢

转载自www.cnblogs.com/LMCC1108/p/10433164.html