# C - Eight HDU - 1043

5 篇文章 0 订阅

The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let's call the missing tile 'x'; the object of the puzzle is to arrange the tiles so that they are ordered as:

 1  2  3  4
5  6  7  8
9 10 11 12
13 14 15  x


where the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:

 1  2  3  4     1  2  3  4     1  2  3  4     1  2  3  4
5  6  7  8     5  6  7  8     5  6  7  8     5  6  7  8
9  x 10 12     9 10  x 12     9 10 11 12     9 10 11 12
13 14 11 15    13 14 11 15    13 14  x 15    13 14 15  x
r->            d->            r->


The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively.

Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course).

In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three
arrangement.

#### Input

You will receive, several descriptions of configuration of the 8 puzzle. One description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'. For example, this puzzle

1 2 3
x 4 6
7 5 8

is described by this list:

1 2 3 x 4 6 7 5 8

#### Output

You will print to standard output either the word unsolvable'', if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line. Do not print a blank line between cases.

#### Sample

InputcopyOutputcopy
2  3  4  1  5  x  7  6  8

ullddrurdllurdruldr

### 翻译

15拼图已经存在超过100年了，即使你不知道它的名字，你也一定见过它。它由15个滑动瓷砖构成，每个瓷砖上都有一个从1到15的数字，并且所有的瓷砖都紧密地装在一个4x4的框架中，有一个瓷砖缺失。我们称缺失的瓷砖为'x'；这个拼图的目标是将瓷砖排列成如下顺序：

1  2  3  4
5  6  7  8
9 10 11 12
13 14 15  x

1  2  3  4     1  2  3  4     1  2  3  4     1  2  3  4
5  6  7  8     5  6  7  8     5  6  7  8     5  6  7  8
9  x 10 12     9 10  x 12     9 10 11 12     9 10 11 12
13 14 11 15    13 14 11 15    13 14  x 15    13 14 15  x
r->            d->            r->

1 2 3
x 4 6
7 5 8

1 2 3 x 4 6 7 5 8

InputcopyOutputcopy
2  3  4  1  5  x  7  6  8

ullddrurdllurdruldr
​
#include <string>
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>      // 包含 C 风格字符串处理
#include <algorithm>    // 包含常用算法

using namespace std;    // 使用标准命名空间

const int maxn = 5e5 + 10; // 定义最大状态数量
int vis[maxn]; // 定义正向搜索访问标记数组
int vis2[maxn]; // 定义反向搜索访问标记数组
char d[10] = { "udlr" }, d2[10] = { "durl" }; // 定义移动方向字符数组
int dir[4] = { -3, 3, -1, 1 }; // 定义方向对应的索引变化量
int ha[9] = { 40320, 5040, 720, 120, 24, 6, 2, 1, 1 }; // 阶乘预处理数组，用于计算哈希值
string a, b = "123456780"; // 定义初始状态和目标状态字符串

// 记录节点信息的结构体
struct node {
int num; // 当前节点编号
char ch; // 从父节点到当前节点的移动方向
} pre[maxn]; // 定义节点信息数组

// 用于存储状态及其编号的结构体
struct node2 {
int num; // '0'所在的位置
string s; // 状态字符串
} e; // 定义初始状态结构体

// 递归输出解法路径
void show(int x) {
if (pre[x].num == -1) // 如果是初始节点，返回
return;
show(pre[x].num); // 递归输出前一个节点
printf("%c", pre[x].ch); // 输出当前节点的移动方向
}

// 计算状态的哈希值
int getha(string s) {
int sum = 0; // 初始化哈希值
for (int i = 0; i < 9; i++) { // 遍历字符串
int k = 0; // 逆序数计数
for (int j = i + 1; j < 9; j++) { // 计算逆序数
if (s[j] < s[i]) k++;
}
sum += k * ha[i]; // 计算哈希值
}
return sum; // 返回哈希值
}

// 广度优先搜索，包含双向搜索
void bfs() {
int q = getha(e.s); // 计算初始状态的哈希值
queue<node2> q1; // 定义正向搜索队列
queue<node2> q2; // 定义反向搜索队列
vis[q] = 1; // 标记初始状态已访问
node2 f, g; // 定义状态结构体
f.s = b; // 设置目标状态
f.num = 8; // 设置'0'的位置
int p = getha(f.s); // 计算目标状态的哈希值
int x, k; // 定义临时变量
vis2[p] = 2; // 标记目标状态已访问
pre[1].num = -1; // 初始化路径记录
pre[2].num = -1; // 初始化路径记录
int num = 2; // 设置状态编号初始值
q1.push(e); // 初始状态入队
q2.push(f); // 目标状态入队
while (!q1.empty() && !q2.empty()) { // 如果两个队列都不为空
f = q1.front(); // 取出正向搜索队列的队首元素
q1.pop(); // 弹出队首元素
p = getha(f.s); // 计算当前状态的哈希值
if (vis2[p]) { // 如果反向搜索已访问
show(vis[p]); // 输出正向路径
k = vis2[p]; // 取出反向路径起点
while (pre[k].num != -1) { // 输出反向路径
printf("%c", pre[k].ch); // 输出方向字符
k = pre[k].num; // 取下一个节点
}
printf("\n"); // 输出换行
return; // 结束搜索
}
for (int i = 0; i < 4; i++) { // 遍历四个方向
if (i == 0 && f.num < 3) continue; // 如果不能向上移动，跳过
if (i == 1 && f.num > 5) continue; // 如果不能向下移动，跳过
if (i == 2 && f.num % 3 == 0) continue; // 如果不能向左移动，跳过
if (i == 3 && f.num % 3 == 2) continue; // 如果不能向右移动，跳过
x = f.num + dir[i]; // 计算移动后的'0'位置
g = f; // 复制当前状态
swap(g.s[f.num], g.s[x]); // 交换'0'与目标位置
q = getha(g.s); // 计算新状态的哈希值
if (vis[q]) continue; // 如果新状态已访问，跳过
vis[q] = ++num; // 标记新状态已访问，并记录编号
g.num = x; // 更新'0'的位置
pre[num].num = vis[p]; // 记录路径信息
pre[num].ch = d[i]; // 记录移动方向
q1.push(g); // 新状态入队
}
f = q2.front(); // 取出反向搜索队列的队首元素
q2.pop(); // 弹出队首元素
p = getha(f.s); // 计算当前状态的哈希值
if (vis[p]) { // 如果正向搜索已访问
show(vis[p]); // 输出正向路径
k = vis2[p]; // 取出反向路径起点
while (pre[k].num != -1) { // 输出反向路径
printf("%c", pre[k].ch); // 输出方向字符
k = pre[k].num; // 取下一个节点
}
printf("\n"); // 输出换行
return; // 结束搜索
}
for (int i = 0; i < 4; i++) { // 遍历四个方向
if (i == 0 && f.num < 3) continue; // 如果不能向上移动，跳过
if (i == 1 && f.num > 5) continue; // 如果不能向下移动，跳过
if (i == 2 && f.num % 3 == 0) continue; // 如果不能向左移动，跳过
if (i == 3 && f.num % 3 == 2) continue; // 如果不能向右移动，跳过
x = f.num + dir[i]; // 计算移动后的'0'位置
g = f; // 复制当前状态
swap(g.s[f.num], g.s[x]); // 交换'0'与目标位置
q = getha(g.s); // 计算新状态的哈希值
if (vis2[q]) continue; // 如果新状态已访问，跳过
vis2[q] = ++num; // 标记新状态已访问，并记录编号
g.num = x; // 更新'0'的位置
pre[num].num = vis2[p]; // 记录路径信息
pre[num].ch = d2[i]; // 记录移动方向
q2.push(g); // 新状态入队
}
}
printf("unsolvable\n"); // 如果队列为空，输出 unsolvable
}

int main() {
while (getline(cin, a)) { // 读取输入行
int n, k = 0; // 定义字符串长度和逆序数计数器
n = a.size(); // 获取输入字符串长度
e.s = ""; // 初始化状态字符串
for (int i = 0, j = 0; i < n; i++) { // 遍历输入字符串
if (a[i] != ' ') { // 如果不是空格
if (a[i] == 'x') { // 如果是'x'
e.num = j; // 记录'0'的位置
e.s += '0'; // 将'x'转换为'0'
} else {
e.s += a[i]; // 添加字符到状态字符串
}
j++; // 字符计数器加1
}
}
for (int i = 0; i < 9; i++) { // 遍历状态字符串
if (e.s[i] == '0') continue; // 如果是'0'跳过
for (int j = 0; j < i; j++) { // 计算逆序数
if (e.s[j] == '0') continue; // 如果是'0'跳过
if (e.s[j] > e.s[i]) k++; // 如果前面的数比当前数大，逆序数加1
}
}
memset(vis2, 0, sizeof(vis2)); // 重置反向访问标记数组
memset(vis, 0, sizeof(vis)); // 重置正向访问标记数组
if (k & 1) { // 如果逆序数为奇数
printf("unsolvable\n"); // 输出 unsolvable
} else {
bfs(); // 否则进行广度优先搜索
}
}
return 0; // 程序结束
}

​

• 28
点赞
• 28
收藏
觉得还不错? 一键收藏
• 打赏
• 0
评论
08-04 1546
12-07 326
02-08 666
09-05 404
09-06 628
09-06 919
09-07 606
09-03 1377
09-08 243

Ws＿

¥1 ¥2 ¥4 ¥6 ¥10 ¥20

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