第十二届蓝桥杯 C/C++ B组 E题 路径:题解|刷题打卡

·  阅读 97
第十二届蓝桥杯 C/C++ B组 E题 路径:题解|刷题打卡

本文正在参与掘金团队号上线活动,点击 查看大厂春招职位

一、题目描述:

今天要写的这道题是今天新鲜出炉的第十二届蓝桥杯 C/C++ B 组的 E 题路径,看网上很多人都说今年的题目要难不少,但是大部分题目考察的还是经典的简单算法,比如这道题。

题目大意: 一共有 20212021 个点,标号从 1120212021,用 graph[i][j]graph[i][j] 表示点 ii 到点 jj 的边,则有:

graph[i][j]={lcm(i,j),abs(ij)21inf,elsegraph[i][j] = \begin{cases} lcm(i, j), \quad abs(i - j) \leq 21\\ inf, \quad else \end{cases}

其中 infinf 表示边权无穷大,也就是没有边相连,lcmlcm 表示最小公倍数,absabs 表示绝对值。

现在问你,从点 11 到点 20212021 的最短路径长度是多少?

二、思路分析:

这道题的解法十分简单,说白了就两步:1. 建图,2. 求最短路。

  1. 建图:
    因为这道题的数据范围很小,我们可以直接用邻接矩阵来表示图,还有计算边权的时候需要知道一个数学知识:lcm(a,b)=ab/gcd(a,b)lcm(a, b) = a * b / gcd(a, b),其中 gcdgcd 表示最大公约数。

  2. 求最短路: 求最短路是图论中十分基本的问题,求解的方法也有很多,因为这道题只是填空题,所以直接选用最简单的 DijkstraDijkstra 算法求解。该算法的核心思想是贪心,用 dist[i]dist[i] 表示点 11 到点 ii 的最短路径,初始时令 dist[i]={0,i=1inf,elsedist[i] = \begin{cases} 0, \quad i = 1\\ inf, \quad else \end{cases},之后每一步都选取出未被标记的点中 distdist 最小的点 xx,标记 xx,再用 min(dist[y],dist[x]+graph[x][y])min(dist[y], dist[x] + graph[x][y]) 更新 xx 得到所有后继 yy,直到所有点都被标记。

这么做的正确性可以这样理解:第一步肯定是从 distdist00 的点 11 出发,然后更新从点 11 只经过一条边到其他点的最短路,然后我们再从其它点中找到最小的 dist[x]dist[x],假设 yy 表示其他点中的任意一点,那么有 dist[y]dist[x]dist[y] \geq dist[x],肯定就会有 dist[y]+graph[y][x]dist[x]dist[y] + graph[y][x] \geq dist[x]。这表示不存在这样的 yy,使得先从 11yy 再到 xx 的花费比 dist[x]dist[x] 更小,所以 dist[x]dist[x] 一定是 11xx 的最短路径。除了 xx 其他点 xx' 不一定满足 dist[x]dist[y],y{其它未被标记点}dist[x'] \leq dist[y], \quad y \in \{其它未被标记点\},也就是说 dist[x]dist[x'] 不一定是 11xx' 的最短路,所以可以被更新。这也说明了,如果边权存在负数,DijkstraDijkstra 算法就不适用了,但那已不在本题的讨论范围内了。

三、AC 代码:

/* 
 * 第十二届蓝桥杯 C/C++ B组 E题 路径
 */

#include <iostream>
#include <cstring>
using namespace std;
using ll = long long;

const int maxn = 2077;
const int inf = 0x3f3f3f3f;
int graph[maxn][maxn], dist[maxn];
bool mark[maxn];
const int n = 2021;

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

int lcm(int a, int b) {
    return a * b / gcd(a, b);
}

void buildGraph() {
    memset(graph, 0x3f, sizeof(graph));
    for (int i = 1; i <= n; ++i) {
        for (int j = max(1, i - 21); j <= min(n, i + 21); ++j) {
            graph[i][j] = graph[j][i] = lcm(i, j);
        }
    }
    for (int i = 1; i <= n; ++i) {
        graph[i][i] = 0;
    }
}

void dijkstra() {
    memset(dist, 0x3f, sizeof(dist));
    dist[1] = 0;
    // 因为每次都是基于已知的一个最短路的点推出至少另一个点的最短路
    // 一开始就知道 dist[1] = 0,所以只需要更新 n - 1 次
    for (int i = 1; i < n; ++i) {
        int x = 0;
        for (int j = 1; j <= n; ++j) {
            if (!mark[j] && (x == 0 || dist[j] < dist[x])) {
                x = j;
            }
        }
        // 记得标记
        mark[x] = true;
        for (int y = 1; y <= n; ++y) {
            dist[y] = min(dist[y], dist[x] + graph[x][y]);
        }
    }
}

int main() {
    buildGraph();
    dijkstra();
    cout << dist[n] << endl;
}
复制代码

四、总结:

对了,答案是:10266837。参加了今天上午比赛的同学可以对对答案。

分类:
代码人生
标签:
分类:
代码人生
标签:
收藏成功!
已添加到「」, 点击更改