当青训营遇上码上掘金 主题3:寻友之旅题解

84 阅读3分钟

当青训营遇上码上掘金

大致题意

小青(以下简称xq)目前在 n 位置,小码(以下简称xm)目前在 k 位置,xq想去xm身边可以通过三种移动方式:

  • 从 x 到 x + 1的位置
  • 从 x 到 x + 2的位置
  • 从 x 到 x * 2的位置 每移动一次消耗 1 分钟,最少消耗多少分钟可以到达xm的身边。

题解

我们可以这样想,xq在每个位置都有三种移动方式,这就好比这个 x 分别与 x + 1、x - 1、x * 2这三个节点具有一条单向的道路,而且这三条道路之间的距离都可以当作1,xq想去见xm的话就需要通过这些道路同时花费最少的时间即走最短的路线。

此时,我们就得到了一个有向图,并且在这张图上我们要找到xq到xm之间的最短路径。

我们在求到xm的最短路径时因为需要经过其它的点,所以同时也需要求到其它的点最短路径。我们不妨假设xm可能出现在图中的任何位置,这样我们只需要xq到其它任何点的距离都可以了。因为每个点都通往另外 3 个点同时距离都是 1 ,所以在这里用像是用dijkstra、bellman-ford、spfa这类的求单源最短路的算法就太麻烦了的说。

所以呢,我们可以首先用一个容器保存 n 到自己的距离为0。然后从 n 这个起始点出发,保存到n + 1、n - 1、n * 2这三个点的距离,并把 n 从容器中去除因为它已经求完到其它点的距离了,没有其它用了。依次类推,我们就可以得到从 n 出现到其它点的所有最短距离了。其实这个过程,在找到xm即到达 k 点之后就可以停止了。

然后我们来证明这个算法是否合理,从 n 点到每一个点有很多种方法,我们只需要保存最短的就可以,因为我们要通过这个点到其它点的话,肯定需要最短。因此每次都是最短的 + 1,新到的点也是最短的,前提是要保证已经去过的点不要再去了。

参考代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int N = 2e5 + 10;

int n, k;
int d[N];

int bfs() {
    if (k <= n) return (n - k);

    memset(d, -1, sizeof d);
    queue<int> q;
    q.push(n);
    d[n] = 0;

    while (q.size()) {
        int t = q.front();
        q.pop();

        if (t == k) return d[k];

        int dx[] = {-1, 1, t};
        for (int i = 0; i < 3; i++) {
            int x = t + dx[i];
            if (x >= 0 && x <= k * 2 && d[x] == -1) {
                d[x] = d[t] + 1;
                q.push(x);
            }
        }
    }

    return -1;
}

int main() {
    cin >> n >> k;

    cout << bfs();

    return 0;
}

简单介绍以下代码

  1. 首先是边界问题即,xq和xm的最远距离为k - 1,这是如果我们走到了k * 2之外就得不偿失了,因为这一定比直接采用第一种方案花费还要大,同时k的范围为0~1e5,所以这里即这条一维路的长度取最大为2e5 + 10。+10是防止出现边界问题。
  2. 用queue作为存储容器:是因为队列先进先出,已经用过的发点直接出去把新的点加入,符合我们的解题想法。
  3. 如果k在n后面直接往后走,特判即刻。