当青训营遇上码上掘金
大致题意
小青(以下简称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;
}
简单介绍以下代码
- 首先是边界问题即,xq和xm的最远距离为k - 1,这是如果我们走到了k * 2之外就得不偿失了,因为这一定比直接采用第一种方案花费还要大,同时k的范围为0~1e5,所以这里即这条一维路的长度取最大为2e5 + 10。+10是防止出现边界问题。
- 用queue作为存储容器:是因为队列先进先出,已经用过的发点直接出去把新的点加入,符合我们的解题想法。
- 如果k在n后面直接往后走,特判即刻。