C - Eight HDU - 1043

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

其中唯一合法的操作是将'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->

上面一行中的字母表示每一步交换'x'瓷砖的相邻瓷砖;合法的值有'r'、'l'、'u'和'd',分别表示向右、向左、向上和向下。

并不是所有的拼图都能被解决;在1870年,一个名叫Sam Loyd的人因发布一个不可解的拼图而出名,并且让很多人感到沮丧。实际上,要将一个正常的拼图变成一个不可解的拼图,只需要交换两个瓷砖(不包括缺失的'x'瓷砖)。

在这个问题中,你将编写一个程序来解决较不知名的8拼图,它由一个三行三列的瓷砖组成。

输入
你将收到若干个8拼图的配置描述。一个描述只是一个瓷砖在初始位置的列表,其中行从上到下列出,瓷砖在行内从左到右列出,其中瓷砖用数字1到8和'x'表示。例如,这个拼图:

1 2 3
x 4 6
7 5 8

可以描述为这个列表:

1 2 3 x 4 6 7 5 8

输出
你将打印到标准输出要么是“unsolvable”,如果拼图无解;要么是一个完全由字母'r'、'l'、'u'和'd'组成的字符串,描述了一系列产生解的移动。字符串不应包含空格,并且从行首开始。不要在案例之间打印空行。

样例

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; // 程序结束
}

​

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ws_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值