HDU - 1043 Eight
题目
题目是八数码问题,肯定都玩过,输入数据是多组,每次给出初始状态,问能否达到123456780 的状态,如果能就输出上下左右怎么走的路径,不能就输出英文。
分析
1. 自己做法
看了一眼题,感觉 bfs 还可以,因为状态很好表示,直接一个字符串就行,大致就是 b f s + s t r i n g 存 状 态 + m a p 判 重 bfs + string 存状态 + map 判重 bfs+string存状态+map判重 ,结果选 g + + g++ g++ 交 M L E MLE MLE,选 c + + c++ c++ 交 T L E TLE TLE。直接 G G GG GG。代码:https://paste.ubuntu.com/p/XX6pCTWjSx/
2. “无解”的判断
搜了题解之后才发现,这题还有可能无解。设八数码要从 A 状态到 B 状态。状态就用一维的数组表示。例如 123046758 。0 位置代表空。求出两个状态的逆序对数(不包括 0),如果两个状态逆序对数奇偶性相同则可相互到达。本题的最终状态就是 123456780 。也就是逆序对数为零,偶数。
参考:判断无解条件
3. 康拓展开
再来要用到的就是康拓展开,他能将一组数的全排列编号,并对应。一般用于全排列的哈希处理。。第一次学,先看百度百科:
其实就是一个公式:
X
=
A
n
∗
(
n
−
1
)
!
+
A
n
−
1
∗
(
n
−
2
)
!
+
…
+
A
1
∗
0
!
X = A_n * (n-1)! + A_{n-1} * (n-2)! + … + A_1 * 0!
X=An∗(n−1)!+An−1∗(n−2)!+…+A1∗0!
A
i
A_i
Ai 代表在当前排列方式下的,
A
i
A_i
Ai 在下标为
i
−
n
i - n
i−n中是第几大,
4.大神做法
关于这个问题博客还是很多的,例如 八种方法解决八数码,搜索方法可以用双向BFS、A*、IDEA。映射方法可以哈希、cantor。反正方法有很多种,而且一个比一个高大上,好像这个问题还是是人工智能课程的小作业。
5.效率比较
提交了几组代码发现了最快的方法,171ms,虽然看杭电还有 0 ms的做法,不过看不到代码。
大致是这样做的:
因为是多组输入,如果每一个都从当前状态搜索终点,太浪费时间,因为终点已经定了,就是 123456780。可以直接从终点出发逆向搜索。找到从终点状态可以到达的所有状态,也就是打表出终点的搜索树。针对某一组的状态,直接看有没有在搜索树中出现,直接输出路径,完美。。。
代码
#include <cstdio>
#include <iostream>
#include <queue>
#include <string>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
#define ll long long
#define d(x) cout<<(x)<<endl
const int N = 370000;
int a[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int dir[][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
char di[4] = {'l', 'r', 'u', 'd'};
int fac[10];
struct tree{
char way;
int fa;
} tr[N]; // 所有情况,搜索树
typedef struct queue_node{
int cur[10]; // 当前状态
int n; // X 所在位置值
int son; // 康拓值
} node;
void init(){
for(int i = 0; i < N; i++){ // 预处理
tr[i].fa = -1;
}
fac[0] = 1;
for(int i = 1; i <= 8; i++){ // 打表阶乘
fac[i] = fac[i - 1] * i;
}
}
int cantor(int *arr){ // 康拓展开
int ans = 0, k = 0;
for(int i = 0; i < 9; i++){
k = 0;
for (int j = i + 1; j < 9; j++)
if(arr[i] > arr[j])
k++;
ans += k * fac[8 - i];
}
return ans;
}
void bfs(){
queue<node> q;
node s;
for(int i = 0; i < 9; i++) // 从最终状态开始找
s.cur[i] = a[i];
s.n = 8;
s.son = 0;
tr[s.son].fa = 0; // 整棵树的根
q.push(s);
while(!q.empty()){
node cnt = q.front();
q.pop();
for(int i = 0; i < 4; i++){
node next = cnt;
int tx = cnt.n % 3 + dir[i][0];
int ty = cnt.n / 3 + dir[i][1];
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3){
next.n = ty * 3 + tx;
swap(next.cur[next.n], next.cur[cnt.n]);
next.son = cantor(next.cur);
if(tr[next.son].fa == -1){ // 之前没有到达的状态
tr[next.son].fa = cnt.son; // 加入树
tr[next.son].way = di[i];
q.push(next);
}
}
}
}
}
void solve(){
char ch;
int k = 0, arr[10];
while(~(ch = getchar())){
if(ch == ' ')
continue;
if (ch != '\n'){
arr[k++] = (ch=='x'?9:ch-'0');
}else{
int cnt = cantor(arr); // 初始康拓值
if(tr[cnt].fa == -1){
printf("unsolvable\n");
}else{
while(cnt != 0){
printf("%c", tr[cnt].way);
cnt = tr[cnt].fa;
}
puts("");
}
k = 0;
}
}
}
int main()
{
init(); // 预处理
bfs(); // 从答案反推搜索树
solve(); // 对每组数据进行处理
return 0;
}
/*
2 3 4 1 5 x 7 6 8
1 2 3 4 5 6 7 8 x
1 2 3 4 x 6 7 5 8
ullddrurdllurdruldr
rrdluldrrulddrulu
ldruullddrurdllurrd
5 5 5
1 2 3 4 5 6 7 8 x
2 1 4 3 5 x 6 8 7
unsolvable
2 1 4 3 5 x 6 8 7
drdlurdruldruuldlurrdd
8 5 6 4 x 3 4 1 2
rulddruulddluurddrulldrurd
8 5 6 4 x 3 4 1 2
urdluldrurdldruulddluurddr
*/