题解 P1379 八数码难题

题目描述

Link

\(3\times 3\) 的方格里面,放置了 \(1 \sim 8\)\(8\) 个数字,有一个空格。

每次操作可以把空格和上下左右中的一个格子交换位置,给定一个初始状态,问让这个状态变成终止状态最少需要多少步。

状态中的空格都用 0 表示。

终止状态是:

1 2 3
8 0 4
7 6 5

Solution

虽然本题不需要判断无解,但是其实无解的条件就是把这个棋盘按照行读下来(空格跳过),逆序对的数量是奇数。

证明比较复杂,可以参考《算法竞赛进阶指南》。

考虑朴素的搜索:使用 bfs ,每次可以把当前的局面把空格向四个方向移动,然后继续搜索。

直接搜索时间开销很大,考虑优化:

  • 开个 dist 数组保存每个状态的最小步数,每次仅当拓展之后的步数比原本的步数更优才拓展。

  • 使用 \(A*\) ,因为估价函数要比真实的答案小,并且要尽量接近真实的答案,所以可以设成每个数字的当前位置到它的目标位置的曼哈顿距离。然后每次搜索的时候取出当前所有待考虑决策中 已走步数 \(+\) 估价函数 值最小的一个进行拓展,用优先队列解决。

基本思路就是这样。至于怎么存下一个棋盘的信息,可以用 string 横向存下来,然后用一个 map / unordered_map 存下每个状态的 dist

代码如下:

#include <iostream>
#include <algorithm>
#include <string>
#include <queue>
#include <cmath>
#include <unordered_map>
using namespace std;
char g[4][4];
inline void put(string s) {
    for (int i = 1; i <= 3; i++)
        for (int j = 1; j <= 3; j++)
            g[i][j] = s[(i - 1) * 3 + j - 1];
}
inline string get() {
    string s;
    for (int i = 1; i <= 3; i++)
        for (int j = 1; j <= 3; j++)
            s += g[i][j];
    return s;
}
inline int getdist(int x1 ,int y1 ,int x2 ,int y2) {
    return abs(x2 - x1) + abs(y2 - y1);
}
pair <int ,int> posit[9];
inline void init(string s = "123804765") {
    for (int c = 0 ,i = 1; i <= 3; i++)
        for (int j = 1; j <= 3; j++ ,c++) {
            if (s[c] == '0') continue;
            posit[s[c] - '0'] = make_pair(i ,j);
        }
}
struct node {
    string now; int dis ,f;
    node (string now = "" ,int dis = 0 ,int f = 0) : now(now) ,dis(dis) ,f(f) {}
    inline void set() {
        for (int i = 1; i <= 3; i++)
            for (int j = 1; j <= 3; j++) {
                if (g[i][j] == '0') continue;
                int c = g[i][j] - '0';
                f += getdist(i ,j ,posit[c].first ,posit[c].second);
            }
    }
    friend bool operator < (node a ,node b) {
        return a.dis + a.f > b.dis + b.f; //注意 STL 默认是大根堆,如果要定义小根堆需要反向定义
    }
};
const string ed = "123804765"; //目标状态
const int dx[] = {0 ,-1 ,1 ,0 ,0} ,dy[] = {0 ,0 ,0 ,-1 ,1};
inline int Astar(string st) {
    unordered_map <string ,int> distan;
    priority_queue <node> q;
    distan[st] = 0;
    node t = node(st ,0 ,0); t.set();
    if (st == ed) return 0;
    q.push(t);
    while (!q.empty()) {
        string now = q.top().now; int dis = q.top().dis; q.pop();
        if (now == ed) return dis;
        put(now);
        int x = 0 ,y = 0;
        for (int i = 1; i <= 3; i++) //找到空格的位置
            for (int j = 1; j <= 3; j++)
                if (g[i][j] == '0') {
                    x = i ,y = j; break;
                }
        for (int i = 1; i <= 4; i++) {
            int tx = x + dx[i] ,ty = y + dy[i];
            if (tx < 1 || ty < 1 || tx > 3 || ty > 3) continue;
            swap(g[x][y] ,g[tx][ty]); //移动就相当于交换。
            string s = get();
            if (s == ed) return dis + 1;
            if (!distan.count(s)) {
                t = node(s ,dis + 1 ,0); t.set();
                distan[s] = dis + 1;
                q.push(t);
            }
            swap(g[x][y] ,g[tx][ty]);
        }
    }
    return -1;
}
string st;
signed main() {
    ios :: sync_with_stdio(false);
    cin >> st;
    init();
    cout << Astar(st) << endl;
    return 0;
}
posted @ 2021-04-27 21:08  recollector  阅读(67)  评论(0编辑  收藏  举报