题目链接
题目描述
在一个有 个城市和
条双向道路的王国中,计算从城市 1 到城市
的最小通行时间。
- 每条道路连接城市
和
,基础通行时间为
。
- 每个城市
有一个魔法石,能量值为
。
- 坏魔法石 (
):强制生效,增加通行时间。
- 好魔法石 (
):可以选择是否生效来减少通行时间。
- 限制:在单次行程中,好魔法石的使用总次数不能超过
次。
- 城市和道路可以重复经过。如果无法到达,输出
NO
。
解题思路
本题是一个带有负权边的状态最短路问题。由于 SPFA 算法在某些数据下会超时,最高效稳妥的解法是分层图最短路。
- 状态定义:
表示到达城市
,至多使用了
次好魔法石所花费的最小时间。
- 分层计算:我们按
(已用次数) 从 0 到
的顺序,逐层计算最短路径。
- 继承:为了满足“至多”
次的定义,在计算第
层前,我们先让它继承第
层的最优解:
dist[i][k] = dist[i][k-1]
。 - 层内移动 (Dijkstra):在同一层
内,从
到
的移动不增加好魔法石使用次数。成本为
w + max(0, a[v])
(路费 + 目的地v
的坏魔法石惩罚)。此成本非负,可在该层内安全地使用 Dijkstra 算法更新最短路。 - 层间转移:当我们算完第
层的最短路后,就可以用它来更新第
层。如果从
(在第
层)移动到
,且
有好魔法石(
),我们可以选择使用它,这就构成了一次到
层的转移。
dist[v][k+1]
可以由dist[u][k] + w + a[v]
更新。
- 继承:为了满足“至多”
算法流程:
- 初始化
数组为无穷大,
。
- 循环
从 0 到
: a. 继承:
dist[i][k+1] = dist[i][k]
。 b. 层内 Dijkstra:在第层上运行 Dijkstra 算法。 c. 层间转移:用第
层的结果去更新第
层的距离。
- 在计算完所有层后,对最后一层
再运行一次 Dijkstra,以确保层内的最优解被充分传播。
- 最终答案是
。
代码
#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <algorithm>
using namespace std;
const long long INF = 1e18;
void dijkstra(int n, int k_level, vector<vector<long long>>& dist, const vector<long long>& a, const vector<vector<pair<int, int>>>& adj) {
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
for (int i = 1; i <= n; ++i) {
if (dist[i][k_level] != INF) {
pq.push({dist[i][k_level], i});
}
}
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u][k_level]) {
continue;
}
for (auto& edge : adj[u]) {
int v = edge.first;
int w = edge.second;
long long cost = w + (a[v] >= 0 ? a[v] : 0);
if (dist[u][k_level] != INF && dist[u][k_level] + cost < dist[v][k_level]) {
dist[v][k_level] = dist[u][k_level] + cost;
pq.push({dist[v][k_level], v});
}
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m, k;
cin >> n >> m >> k;
vector<long long> a(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<vector<long long>> dist(n + 1, vector<long long>(k + 1, INF));
dist[1][0] = 0;
dijkstra(n, 0, dist, a, adj);
for (int k_used = 0; k_used < k; ++k_used) {
// 继承
for(int i = 1; i <= n; ++i) {
dist[i][k_used + 1] = dist[i][k_used];
}
// 转移
for (int u = 1; u <= n; ++u) {
if (dist[u][k_used] != INF) {
for (auto& edge : adj[u]) {
int v = edge.first;
int w = edge.second;
if (a[v] < 0) {
dist[v][k_used + 1] = min(dist[v][k_used + 1], dist[u][k_used] + w + a[v]);
}
}
}
}
// Dijkstra on new level
dijkstra(n, k_used + 1, dist, a, adj);
}
long long min_dist = dist[n][k];
if (min_dist == INF) {
cout << "NO\n";
} else {
cout << min_dist << '\n';
}
return 0;
}
import java.util.*;
public class Main {
static class State implements Comparable<State> {
long time;
int city;
State(long time, int city) {
this.time = time;
this.city = city;
}
@Override
public int compareTo(State other) {
return Long.compare(this.time, other.time);
}
}
static class Edge {
int to;
int weight;
Edge(int to, int weight) {
this.to = to;
this.weight = weight;
}
}
private static void dijkstra(int n, int kLevel, long[][] dist, long[] a, List<List<Edge>> adj) {
PriorityQueue<State> pq = new PriorityQueue<>();
for (int i = 1; i <= n; i++) {
if (dist[i][kLevel] != Long.MAX_VALUE) {
pq.add(new State(dist[i][kLevel], i));
}
}
while (!pq.isEmpty()) {
State current = pq.poll();
long d = current.time;
int u = current.city;
if (d > dist[u][kLevel]) {
continue;
}
for (Edge edge : adj.get(u)) {
int v = edge.to;
int w = edge.weight;
long cost = w + (a[v] >= 0 ? a[v] : 0);
if (dist[u][kLevel] != Long.MAX_VALUE && dist[u][kLevel] + cost < dist[v][kLevel]) {
dist[v][kLevel] = dist[u][kLevel] + cost;
pq.add(new State(dist[v][kLevel], v));
}
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int k = sc.nextInt();
long[] a = new long[n + 1];
for (int i = 1; i <= n; i++) {
a[i] = sc.nextLong();
}
List<List<Edge>> adj = new ArrayList<>();
for (int i = 0; i <= n; i++) {
adj.add(new ArrayList<>());
}
for (int i = 0; i < m; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
int w = sc.nextInt();
adj.get(u).add(new Edge(v, w));
adj.get(v).add(new Edge(u, w));
}
long[][] dist = new long[n + 1][k + 1];
for (long[] row : dist) {
Arrays.fill(row, Long.MAX_VALUE);
}
dist[1][0] = 0;
dijkstra(n, 0, dist, a, adj);
for (int kUsed = 0; kUsed < k; kUsed++) {
// 继承
for(int i = 1; i <= n; i++) {
dist[i][kUsed + 1] = dist[i][kUsed];
}
// 转移
for (int u = 1; u <= n; u++) {
if (dist[u][kUsed] != Long.MAX_VALUE) {
for (Edge edge : adj.get(u)) {
int v = edge.to;
int w = edge.weight;
if (a[v] < 0) {
dist[v][kUsed + 1] = Math.min(dist[v][kUsed + 1], dist[u][kUsed] + w + a[v]);
}
}
}
}
dijkstra(n, kUsed + 1, dist, a, adj);
}
long minDist = dist[n][k];
if (minDist == Long.MAX_VALUE) {
System.out.println("NO");
} else {
System.out.println(minDist);
}
}
}
import heapq
import collections
def dijkstra(n, k_level, dist, a, adj):
pq = []
for i in range(1, n + 1):
if dist[i][k_level] != float('inf'):
heapq.heappush(pq, (dist[i][k_level], i))
while pq:
d, u = heapq.heappop(pq)
if d > dist[u][k_level]:
continue
for v, w in adj[u]:
cost = w + (a[v] if a[v] >= 0 else 0)
if dist[u][k_level] != float('inf') and dist[u][k_level] + cost < dist[v][k_level]:
dist[v][k_level] = dist[u][k_level] + cost
heapq.heappush(pq, (dist[v][k_level], v))
def solve():
n, m, k = map(int, input().split())
a = [0] + list(map(int, input().split()))
adj = collections.defaultdict(list)
for _ in range(m):
u, v, w = map(int, input().split())
adj[u].append((v, w))
adj[v].append((u, w))
inf = float('inf')
dist = [[inf] * (k + 1) for _ in range(n + 1)]
dist[1][0] = 0
dijkstra(n, 0, dist, a, adj)
for k_used in range(k):
# 继承
for i in range(1, n + 1):
dist[i][k_used + 1] = dist[i][k_used]
# 转移
for u in range(1, n + 1):
if dist[u][k_used] != inf:
for v, w in adj[u]:
if a[v] < 0:
dist[v][k_used + 1] = min(dist[v][k_used + 1], dist[u][k_used] + w + a[v])
dijkstra(n, k_used + 1, dist, a, adj)
min_dist = dist[n][k]
if min_dist == inf:
print("NO")
else:
print(min_dist)
solve()
算法及复杂度
- 算法:分层图最短路。该算法通过将“使用好魔法石的次数”作为分层标准,将原问题分解为
个阶段。在每个阶段内部,由于边权非负(层内移动),可以使用高效的 Dijkstra 算法求解。
- 时间复杂度:外层循环执行
次,每次循环的核心是一次 Dijkstra 算法。Dijkstra 的复杂度为
。因此,总时间复杂度为
。
- 空间复杂度:主要由邻接表和
数组决定。邻接表需要
的空间,
数组需要
的空间。因此,总空间复杂度为
。