题解 | 算法竞赛进阶指南-八数码

B-八数码_0x27 搜索-A*

https://ac.nowcoder.com/acm/contest/1019/B

题目描述

在一个3×3的网格中,1~8这8个数字和一个“X”恰好不重不漏地分布在这3×3的网格中。

例如:

1 2 3
X 4 6
7 5 8

在游戏过程中,可以把“X”与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 X

例如,示例中图形就可以通过让“X”先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3    r   1 2 3    d   1 2 3    r   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

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次怎样的交换。


输入样例:

2  3  4  1  5  x  7  6  8 

输出样例:

ullddrurdllurdruldr

分析

本题求最少步数,所以应当用bfs来做

首先定义一个能表示矩阵状态的结构体,每次把由当前状态更新的合法的新状态压入队列

如果状态为目标状态,那么返回步数,如果更新不到目标状态,返回-1

我们可以想到,这个3*3的矩阵可以表示为一个长度为9的字符串

但是我们知道,bfs需要把遍历过的状态标记,以防止死循环


那么,如何开辟一个数组

使得这个数组中的元素,能够和矩阵的所有状态(长度为9的字符串的全排列)一一对应

这才是难点

(当然用通用的哈希表也是可行的,只是在本题中效率没有那么高)


康托展开

  • 我们熟知的数一般都是常进制数,所谓常进制数就是该数的每一位都是常数进制的

    进制数上的每一位都逢进一,第位的位权是

  • 这里要介绍一种变进制数,用来表示字符串的排列状态

    这种数的第位逢进一,第位的位权是

    来表示一个变进制数第位上的数字

    一个位变进制数的值就为

这是一个最大的9位变进制数

876543210

它对应的十进制数为

8 × 8! + 7 × 7! + 6 × 6! + …… + 1 × 1! + 0 × 0! = 9! - 1 = 362879

我们可以找到一个9位变进制数,与一个9位无重复串的某种排列一一对应

表示字符串中的第位与其前面的字符组成的逆序对个数

字符串的一种排列对应的变进制数的值为

这是字符串123x46758的与的对应关系

  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)       //求长度为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 ++;          //求s[i]与其前面的字符组成的逆序对个数
        ans += d * fact[i];
    }
    return ans;
}
  • n不能太大,通常不超过12,否则会溢出
  • 这一步时间复杂度为O(n²)

另外这题似乎没说清rlud的优先级,我试了两手改成uldr才过了样例

全排列哈希 + BFS

C++ 代码

#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

int fact[9];
bool vis[362880];

int permutation_hash(char s[])          //求长度为9的字符串某种排列的哈希值
{
    int ans = 0;
    for(int i = 0; i < 9; i ++)
    {
        int d = 0;
        for(int j = 0; j < i; j ++)
            if(s[j] > s[i])  d ++;      //求s[i]与其前面的字符组成的逆序对个数
        ans += d * fact[i];
    }
    return ans;
}

typedef struct{
    char s[10];
    string step;
    int k;          //'x'在第k位
}Point;

char dir[5] = "uldr";
int dx[4] = {-1, 0, 1, 0};
int dy[4] = { 0,-1, 0, 1};
string bfs(Point p)
{
    vis[permutation_hash(p.s)] = true;
    queue<Point> q;
    q.push(p);
    while(!q.empty())
    {
        p = q.front();
        q.pop();
        if(!strcmp(p.s , "12345678x"))  return p.step;

        int x = p.k / 3;      //'x'的行数
        int y = p.k % 3;      //'x'的列数
        Point next;
        for(int i = 0; i < 4; i ++)
        {
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(nx >= 0 && nx <= 2 && ny >= 0 && ny <= 2)
            {
                next.step = p.step + dir[i];
                next.k = nx * 3 + ny;           //求出'x'在字符串中的的新位置

                strcpy(next.s, p.s);
                next.s[9] = 0;
                next.s[p.k] = p.s[next.k];      //先用即将和'x'交换的字符覆盖'x'之前的位置
                next.s[next.k] = 'x';           //再给'x'的新位置赋值'x'

                int hash = permutation_hash(next.s);
                if(!vis[hash])
                {
                    vis[hash] = true;
                    q.push(next);
                }
            }
        }
    }
    return "unsolvable";
}

int main()
{
    fact[0] = 1;
    for(int i = 1; i < 9; i ++)  fact[i] = fact[i - 1] * i;    //预处理fact[i] = i!

    char c[2],str[10];
    Point start;
    for(int i = 0; i < 9; i ++)
    {
        scanf("%s",&c);
        if(c[0] == 'x')  start.k = i;
        start.s[i] = c[0];
    }
    start.s[9] = 0;
    start.step = "";
    cout << bfs(start);
    return 0;
}
全部评论
ulrd的优先级不影响判题的
1 回复 分享
发布于 2021-04-27 20:19

相关推荐

最近群里有很多同学找我看简历,问问题,主要就是集中在明年三月份的暑期,我暑期还能进大厂嘛?我接下来该怎么做?对于我来说,我对于双非找实习的一个暴论就是title永远大于业务,你在大厂随随便便做点慢SQL治理加个索引,可能就能影响几千人,在小厂你从零到一搭建的系统可能只有几十个人在使用,量级是不一样的。对双非来说,最难的就是约面,怎么才能被大厂约面试?首先这需要一点运气,另外你也需要好的实习带给你的背书。有很多双非的同学在一些外包小厂待了四五个月,这样的产出有什么用呢?工厂的可视化大屏业务很广泛?产出无疑是重要的,但是得当你的实习公司到了一定的档次之后,比如你想走后端,那么中厂后端和大厂测开的选择,你可以选择中厂后端(注意,这里的中厂也得是一些人都知道的,比如哈啰,得物,b站之类,不是说人数超过500就叫中厂),只有这个时候你再去好好关注你的产出,要不就无脑大厂就完了。很多双非同学的误区就在这里,找到一份实习之后,就认为自己达到了阶段性的任务,根本不再投递简历,也不再提升自己,玩了几个月之后,美其名曰沉淀产出,真正的好产出能有多少呢?而实际上双非同学的第一份实习大部分都是工厂外包和政府外包!根本无产出可写😡😡😡!到了最后才发现晚了,所以对双非同学来说,不要放过任何一个从小到中,从中到大的机会,你得先有好的平台与title之后再考虑你的产出!因为那样你才将将能过了HR初筛!我认识一个双非同学,从浪潮到海康,每一段都呆不久,因为他在不断的投递和提升自己,最后去了美团,这才是双非应该做的,而我相信大部分的双非同学,在找到浪潮的那一刻就再也不会看八股,写算法,也不会打开ssob了,这才是你跟别人的差距。
迷茫的大四🐶:我也这样认为,title永远第一,只有名气大,才有人愿意了解你的简历
双非本科求职如何逆袭
点赞 评论 收藏
分享
评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务