📅  最后修改于: 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)$。