1 条题解
-
0
题意分析
题目背景
本题是经典的8拼图问题(3×3九宫格),属于状态空间搜索类问题。拼图由数字1-8和一个空白格('x')组成,目标是通过滑动方块使拼图达到目标状态:
1 2 3 4 5 6 7 8 x
核心问题
- 可解性判定:
并非所有初始状态都能通过合法移动达到目标状态。需要数学方法预先判断是否有解。 - 最短路径搜索:
若可解,需找到最少步数的移动序列(用u
、d
、l
、r
表示空白格的移动方向)。
数学基础
-
排列的逆序数:
将拼图展开为一维数组(忽略'x'),计算逆序对数。若逆序数为偶数,则问题有解;否则无解。
公式:
其中\(P\)为数字排列,\(\text{inv}(P)\)为逆序数。 -
康托展开:
用于将拼图状态映射为唯一整数(状态压缩),便于搜索时判重。
公式:
$ \text{Cantor}(a) = \sum_{i=1}^{9} (d_i \times (9-i)!) $ \(d_i\)为\(a[i]\)右侧比它小的数字个数。
解题思路
1. 可解性判断
- 步骤:
- 将拼图展开为线性序列(如
[1,2,3,x,4,6,7,5,8]
),移除'x'。 - 计算剩余数字的逆序数。
- 若逆序数为偶数,可解;否则输出
unsolvable
。
- 将拼图展开为线性序列(如
2. 广度优先搜索(BFS)
- 状态表示:
使用结构体存储当前拼图状态、空白格位置,并用康托展开唯一标识状态。 - 搜索过程:
- 从初始状态出发,用队列逐层扩展。
- 每次移动空白格(上、下、左、右),生成新状态。
- 若新状态未访问过,记录路径并入队。
- 到达目标状态时,回溯路径输出移动序列。
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
- 上传者