AcWing 845. 八数码
. 八数码
一、题目描述
在一个 的网格中, 这 个数字和一个 恰好不重不漏地分布在这 的网格中。
例如:
1 2 3
x 4 6
7 5 8
在游戏过程中,可以把 x
与其上、下、左、右四个方向之一的数字交换(如果存在)。
我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 x
例如,示例中图形就可以通过让 x
先后与右、下、右三个方向的数字交换成功得到正确排列。
交换过程如下:
1 2 3 1 2 3 1 2 3 1 2 3
x 4 6 4 x 6 4 5 6 4 5 6
7 5 8 7 5 8 7 x 8 7 8 x
现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。
输入格式
输入占一行,将 3×3
的初始网格描绘出来。
例如,如果初始网格如下所示:
1 2 3
x 4 6
7 5 8
则输入为:1 2 3 x 4 6 7 5 8
输出格式
输出占一行,包含一个整数,表示最少交换次数。
如果不存在解决方案,则输出 −1
。
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
19
二、理解与感悟
-
为什么将二维转一维?
输入的是一个一维数据,就像是一个字符串。二维数组如果想对比每一次走完的棋盘是否一致,需要双重循环,比较麻烦,所以想出了一个用一维模拟二维的方法。 -
二维怎么转一维?
一维下标 =tx * 3 + ty
以行号、列号下标从开始为例,就是行号 列号,比如:
行号为(第二行),列号为(第二列),那么,就是在一维中是第个(下标从开始)。
- 一维如何还原成二维?
int x = k / 3, y = k % 3;
三、实现代码
#include <bits/stdc++.h>
using namespace std;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
string ed = "12345678x";
unordered_map<string, int> d;
int bfs(string s) {
queue<string> q;
q.push(s);
d[s] = 0;
while (q.size()) {
auto u = q.front();
q.pop();
if (u == ed) return d[u];
int k = u.find('x');
int x = k / 3, y = k % 3; // 一维变二维,准备变更,判断变更后会不会出界
for (int i = 0; i < 4; i++) {
int tx = x + dx[i], ty = y + dy[i];
if (tx < 0 || tx > 2 || ty < 0 || ty > 2) continue;
string c = u; // 抄出来c
swap(c[k], c[tx * 3 + ty]); // 在c上进行变更,二维变一维
if (!d.count(c)) { // 这个状态没到达过
d[c] = d[u] + 1; // 记录是第几步到达
q.push(c);
}
}
}
return -1;
}
int main() {
string s;
char c;
for (int i = 1; i <= 9; i++) { // 输入的是9个字符,每两个中间有空格
cin >> c;
s += c;
}
cout << bfs(s) << endl;
return 0;
}
四、利用康托计算全排列顺序号的思路
本题求最少步数,所以应当用来做
首先定义一个能表示矩阵状态的结构体,每次把由当前状态更新的合法的新状态压入队列
如果状态为目标状态,那么返回步数,如果更新不到目标状态,返回
我们可以想到,这个的矩阵可以表示为一个长度为的字符串
但是我们知道,需要把遍历过的状态标记,以防止死循环
那么,如何开辟一个数组
使得这个数组中的元素,能够和矩阵的所有状态(长度为的字符串的全排列)一一对应
这才是难点
全排列哈希
- 我们熟知的数一般都是常进制数,所谓常进制数就是该数的每一位都是常数进制的进制数上的每一位都逢进一,第位的位权是
- 这里要介绍一种 变进制数,用来表示字符串的排列状态
- 这种数的第位逢进一,第位的位权是
用来表示一个变进制数第位上的数字
一个位变进制数的值就为
这是一个最大的位变进制数
876543210
它对应的十进制数为
8 × 8! + 7 × 7! + 6 × 6! + …… + 1 × 1! + 0 × 0! = 9! - 1 = 362879
我们可以找到一个位变进制数,与一个位无重复串的某种排列一一对应
用表示字符串中的第位与其前面的字符组成的逆序对个数
字符串的一种排列对应的变进制数的值为
这是字符串的与的对应关系
i 0 1 2 3 4 5 6 7 8
s[i] 1 2 3 x 4 6 7 5 8
d[i] 0 0 0 0 1 1 1 3 1
它对应的变进制数的值为
1 × 4! + 1 × 5! + 1 × 6! + 3 × 7! + 1 × 8! = 56304
因此可以用以下函数求字符串的一种排列对应的哈希值
int permutation_hash(char s[], int n){
int ans = 0;
for(int i = 0; i < n; i ++){
int d = 0;
for(int j = 0; j < i; j ++)
if(s[j] > s[i]) d ++;
ans += d * fact[i];
}
return ans;
}
不能太大,通常不超过,否则会溢出
时间复杂度为
五、全排列哈希 +
#include <bits/stdc++.h>
using namespace std;
struct Node {
string s;
int step;
int k; //'x'在第k位
};
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, -1, 0, 1};
int fact[9];
bool st[362880];
int cantor(string s) { // 求长度为9的字符串某种排列的哈希值
int x = 0;
for (int i = 0; i < 9; i++) {
int smaller = 0;
for (int j = 0; j < i; j++)
if (s[j] > s[i]) smaller++; // 求s[i]与其前面的字符组成的逆序对个数
x += smaller * fact[i];
}
return x;
}
int bfs(Node u) {
st[cantor(u.s)] = 1;
queue<Node> q;
q.push(u);
while (q.size()) {
u = q.front();
q.pop();
if (u.s == "12345678x") return u.step;
int x = u.k / 3; //'x'的行数
int y = u.k % 3; //'x'的列数
Node v;
v.step = u.step + 1;
for (int i = 0; i < 4; i++) {
int tx = x + dx[i], ty = y + dy[i];
if (tx < 0 || tx > 2 || ty < 0 || ty > 2) continue;
// 求出'x'在字符串中的的新位置
v.k = tx * 3 + ty;
// 新串长什么样子
v.s = u.s; // 先抄过来
v.s[u.k] = u.s[v.k]; // 先用即将和'x'交换的字符覆盖'x'之前的位置
v.s[v.k] = 'x'; // 再给'x'的新位置赋值'x'
// 新串的hash值
int hash = cantor(v.s);
if (!st[hash]) {
st[hash] = 1;
q.push(v);
}
}
}
return -1;
}
int main() {
// 预处理fact[i] = i!
fact[0] = 1;
for (int i = 1; i < 9; i++) fact[i] = fact[i - 1] * i;
char c;
Node start;
for (int i = 0; i < 9; i++) {
cin >> c;
if (c == 'x') start.k = i;
start.s.push_back(c);
}
start.step = 0;
printf("%d", bfs(start));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2013-09-18 Apache-系统-网络部分配置