1 条题解

  • 0
    @ 2025-5-3 23:16:13

    题意分析

    题目背景

    本题是经典的8拼图问题(3×3九宫格),属于状态空间搜索类问题。拼图由数字1-8和一个空白格('x')组成,目标是通过滑动方块使拼图达到目标状态:

    1 2 3
    4 5 6
    7 8 x
    

    核心问题

    1. 可解性判定
      并非所有初始状态都能通过合法移动达到目标状态。需要数学方法预先判断是否有解。
    2. 最短路径搜索
      若可解,需找到最少步数的移动序列(用udlr表示空白格的移动方向)。

    数学基础

    • 排列的逆序数
      将拼图展开为一维数组(忽略'x'),计算逆序对数。若逆序数为偶数,则问题有解;否则无解。
      公式
      可解    inv(P)0(mod2) \text{可解} \iff \text{inv}(P) \equiv 0 \pmod{2} 其中\(P\)为数字排列,\(\text{inv}(P)\)为逆序数。

    • 康托展开
      用于将拼图状态映射为唯一整数(状态压缩),便于搜索时判重。
      公式
      $ \text{Cantor}(a) = \sum_{i=1}^{9} (d_i \times (9-i)!) $ \(d_i\)为\(a[i]\)右侧比它小的数字个数。


    解题思路

    1. 可解性判断

    • 步骤
      1. 将拼图展开为线性序列(如[1,2,3,x,4,6,7,5,8]),移除'x'。
      2. 计算剩余数字的逆序数。
      3. 若逆序数为偶数,可解;否则输出unsolvable

    2. 广度优先搜索(BFS)

    • 状态表示
      使用结构体存储当前拼图状态、空白格位置,并用康托展开唯一标识状态。
    • 搜索过程
      1. 从初始状态出发,用队列逐层扩展。
      2. 每次移动空白格(上、下、左、右),生成新状态。
      3. 若新状态未访问过,记录路径并入队。
      4. 到达目标状态时,回溯路径输出移动序列。

    3. 路径记录与输出

    • 数据结构
      使用path[]数组记录每个状态的前驱状态移动方向
    • 回溯方法
      从目标状态反向追踪至初始状态,逆序输出移动方向。

    算法优化

    • 状态压缩:康托展开将9!种状态映射到[0, 362880)的整数,降低存储开销。
    • 双向BFS:可进一步优化,但本题状态空间较小((9! = 362880)),标准BFS已足够高效。

    代码标签

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <queue>
     
    using namespace std;
     
    const int MAXSIZE = 362880;
     
    //The list of factorial:
    int fac[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
     
    //Mark whether this state have been tried:
    bool visited[MAXSIZE];
     
    //The direction of moving:
    int movei[] = {-1, 1, 0, 0};
    int movej[] = {0, 0, -1, 1};
    char direction[] = {'u', 'd', 'l', 'r'};
     
    struct Path
    {
    //how to move to current state(By up, down, left or right):
    char direction;
     
    //Remember how to reach current(from one state to another state):
    int pre;
     
    } path[MAXSIZE];
     
    //Every state:
    struct state
    {
        int i, j;
        char grid[3][3];
    };
     
    //Whether it can be solve:
    bool flag;
     
    void order(char str[][3], int &ans)
    {
        int i, j, k = 0, numOfMin;
        char temp[9];
     
        for(i = 0; i < 3; i++)
            for(j = 0; j < 3; j++){
                temp[k] = str[i][j];
                k++;
        }
     
        ans = 0;
        for(i = 0; i < 8; i++){
                numOfMin = 0;
            for(j = i + 1; j < 9; j++){
                if(temp[i] > temp[j]){
                    numOfMin++;
                }
            }
            ans += fac[8 - i]*numOfMin;
        }
     
    }
     
    void getState(int num, state &ans)
    {
        int i, j, k = 0, numOfMin;
        char temp[9];
     
        for(i = 8; i >= 0; i--){
            numOfMin = num/fac[i];
            temp[8-i] = '1' + numOfMin;
            num = num%fac[i];
        }
     
        for(i = 0; i < 3; i++)
            for(j = 0; j < 3; j++){
                ans.grid[i][j] = temp[k];
                k++;
                if('9' == ans.grid[i][j]){
                    ans.i = i;
                    ans.j = j;
                }
            }
    }
     
    void bfs(state start)
    {
        queue <state> que;
        state now, next;
        int i, j, dir, nextOrder, curOrder;
     
        que.push(start);
        order(start.grid, curOrder);
        path[curOrder].pre = -1;
        visited[curOrder] = true;
        flag = true;
     
        while(!que.empty()){
            now = que.front();
            que.pop();
            order(now.grid, curOrder);
     
            for(dir = 0; dir < 4; dir++){
                i = now.i + movei[dir];
                j = now.j + movej[dir];
                if(i >= 0 && i < 3 && j >= 0 && j < 3){
                    next = now;
                    swap(next.grid[i][j], next.grid[next.i][next.j]);
     
                    order(next.grid, nextOrder);
                    if(!visited[nextOrder]){
                        visited[nextOrder] = true;
                        path[nextOrder].direction = direction[dir];
                        next.i = i, next.j = j;
                        path[nextOrder].pre = curOrder;
                        if(0 == nextOrder)
                            return;
                        que.push(next);
                    }
                }
            }
        }
     
        flag = false;
    }
     
    void printPath()
    {
        char way[1024];
        int k = 0, curOrder = 0, preOrder;
     
        preOrder = path[0].pre;
     
        for(k = 0; path[curOrder].pre != -1; k++){
     
            preOrder = path[curOrder].pre;
     
            way[k] = path[curOrder].direction;
     
            curOrder = preOrder;
        }
     
        for(k--; k >= 0; k--){
            cout<<way[k];
        }
     
        cout<<endl;
    }
    int main()
    {
        //freopen("in.txt", "r", stdin);
     
        int i, j;
     
        state start;
     
        for(i = 0; i < 3; i++)
            for(j = 0; j < 3; j++){
     
            cin>>start.grid[i][j];
     
            if('x' == start.grid[i][j]){
                start.i = i;
                start.j = j;
                start.grid[i][j] = '9';
                }
            }
     
        bfs(start);
        if(flag)
            printPath();
        else
            cout<<"unsolvable\n";
    }
    

    复杂度分析

    • 时间:(O(9!)),即所有可能状态数。
    • 空间:(O(9!)),用于存储访问状态和路径。

    该解法在理论保证下能正确求解所有可解的8拼图问题,并通过逆序数判定提前排除无解情况。

    • 1

    信息

    ID
    78
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者