HDU - 1043 Eight (八数码:逆向BFS + 康拓展开)

22 篇文章 0 订阅
21 篇文章 0 订阅

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(n1)!+An1(n2)!++A10!
A i A_i Ai 代表在当前排列方式下的, A i A_i Ai 在下标为 i − n i - n in中是第几大,

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
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值