📜  门|门CS 2012 |第 35 题(1)

📅  最后修改于: 2023-12-03 15:28:49.030000             🧑  作者: Mango

题目描述

给定一个 n 个点 m 条边的带权有向图,图中可能存在重边和自环, 所有边的权值均为正整数, i 号点到 j 号点最多经过 k 条边,路径权值之和最小。

输入格式

第一行包含三个正整数 n,m,k,其中 k <= n。

接下来的 m 行,每行描述一条带权有向边,格式为 a b c,表示存在一条从点 a 到点 b,权重为 c 的有向边。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短路路径权值和, 如果路径不存在,则输出 −1。

样例

输入样例:

3 3 1
1 2 1
2 1 3
1 3 2

输出样例:

3
分析

根据题目的描述,本题是一道带权有向图的最短路问题,并且需要限定最多经过的边数。

因此,我们可以使用 Dijkstra 算法,结合 BFS 构建带权最短路。

首先,我们需要构建一个三维矩阵 $g$ 来表示图中每个点之间的最短路径,其中 $g[i][j][p]$ 表示当前经过 $p$ 条边从点 $i$ 到点$j$ 的最短距离。

接下来,我们可以使用 Dijkstra 算法和 BFS 对 $g$ 进行更新,具体如下。

我们首先将源点 $s$ 加入队列,并将所有 $g[s][i][0]$ 更新为边权 $w(s, i)$,表示源点到每个点不经过任何边的最短距离。

接着,我们循环找到队列中最小的 $g[s][i][p]$,设当前点为 $u$,并将 $u$ 出队。

然后,我们枚举所有 $u$ 的出边可以到达的下一个点 $v$,如果更新 $g[s][v][p + 1]$ 的值,就将 $v$ 加入队列中。

最后,我们可以得到 $g[s][t][1..k]$ 的值,即为源点 $s$ 到目标点 $t$ 经过 $1..k$ 条边的最短距离值。

详细内容可参考 P2431

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

using namespace std;

typedef pair<int, int> PII;

const int N = 205, M = 40005;
const int INF = 0x3f3f3f3f;

int n, m, k;
int h[N], e[M], ne[M], w[M], idx;
int g[N][N][11];
int dist[N][11];
bool st[N][11];

void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
}

void dijkstra() {
    memset(dist, 0x3f, sizeof dist);
    dist[1][0] = 0;

    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({dist[1][0], 1});

    while (q.size()) {
        auto t = q.top();
        q.pop();

        int u = t.second, p = u % 10;
        u /= 10;

        if (st[u][p]) continue;
        st[u][p] = true;

        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            int np = p + 1;

            if (np > k) continue;

            if (dist[j][np] > dist[u][p] + w[i]) {
                dist[j][np] = dist[u][p] + w[i];
                q.push({dist[j][np], j * 10 + np});
            }
        }
    }

    int res = INF;
    for (int i = 1; i <= k; i ++ )
        res = min(res, dist[n][i]);

    if (res == INF) res = -1;

    printf("%d\n", res);
}

int main() {
    memset(h, -1, sizeof h);

    scanf("%d%d%d", &n, &m, &k);
    while (m -- ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);

        add(a, b, c);
    }

    dijkstra();

    return 0;
}

时间复杂度

Dijkstra 算法的时间复杂度为 $O(m \log n)$,使用 BFS 更新就相当于将节点和边的总数都 * k 了,因此总时间复杂度为 $O(kmn \log n)$。