#P3390. 第3题-奇幻王国魔法石
-
1000ms
Tried: 46
Accepted: 10
Difficulty: 6
所属公司 :
美团
时间 :2025年8月16日-算法岗
-
算法标签>最短路算法
第3题-奇幻王国魔法石
解题思路
-
将状态扩展为分层图:层编号为已使用好石次数 c∈[0,k],状态为 (v,c)。
-
转移分两类(设到达点为 v,边基础代价为 w):
- 不用好石:层内转移 (u,c)→(v,c),代价 w+max(av,0)(坏石必加、好石不加)。
- 用好石(仅当 av<0 且 c<k):跨层 (u,c)→(v,c+1),代价 w+av(可能为负)。
-
由于层内边权均为非负(至少 w≥1),可用 k+1 次 Dijkstra 的分层松弛套路:
-
先在 c=0 层,设 d0[1]=0 其余为无穷,跑一次 Dijkstra(仅用层内转移,代价 w+max(av,0))。
-
对每个 c=0,1,…,k−1:
- 从所有 (u,c) 通过一次“用好石”的跨层边,生成 c+1 层的多源初始距离:dc+1[v]←min(dc+1[v],dc[u]+w+av)(仅当 av<0)。
- 以这些初值为源,在 c+1 层再跑一次 Dijkstra(仍只用层内边 w+max(av,0))完成传递。
-
-
答案为 min0≤c≤kdc[n],若皆为无穷则输出
NO。
正确性简述
- 固定好石使用次数 c 后,剩余路径均为非负边,可用 Dijkstra 正确求得该层最短路。
- 从层 c 到层 c+1 的“多源”跨层初始化,枚举了“恰在某次到达时使用第 c+1 次好石”的所有可能,随后在层内 Dijkstra 覆盖使用完第 c+1 次好石后的任意不使用好石的延伸。
- 逐层推进至 k,覆盖了所有“至多 k 次”使用好石的路径,取最小值即为全局最优。
C++
#include <bits/stdc++.h>
using namespace std;
static const long long INF = (long long)4e18;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
if (!(cin >> n >> m >> k)) return 0;
vector<long long> a(n + 1);
for (int i = 1; i <= n; ++i) cin >> a[i];
vector<vector<pair<int,int>>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
// 层内边的“到达点附加代价”:坏石必加a[v],好石不加(即0)
vector<long long> addStay(n + 1);
for (int v = 1; v <= n; ++v) addStay[v] = max(0LL, a[v]);
auto dijkstra_layer = [&](vector<long long> &dist) {
// 在当前层,仅允许使用“不用好石”的转移:u->v 代价 w + addStay[v]
priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<pair<long long,int>>> pq;
vector<char> vis(n + 1, 0);
for (int i = 1; i <= n; ++i) if (dist[i] < INF) pq.push({dist[i], i});
while (!pq.empty()) {
auto [du, u] = pq.top(); pq.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto [v, w] : g[u]) {
long long nd = du + (long long)w + addStay[v];
if (nd < dist[v]) {
dist[v] = nd;
pq.push({nd, v});
}
}
}
};
// c=0 层:从1出发,不用好石
vector<long long> cur(n + 1, INF);
cur[1] = 0;
dijkstra_layer(cur);
long long ans = cur[n];
for (int used = 0; used < k; ++used) {
// 通过一次“用好石”的跨层边,生成下一层的多源初值
vector<long long> nxt(n + 1, INF);
for (int u = 1; u <= n; ++u) {
if (cur[u] >= INF) continue;
for (auto [v, w] : g[u]) {
if (a[v] < 0) {
long long nd = cur[u] + (long long)w + a[v]; // 使用好石(可能为负)
if (nd < nxt[v]) nxt[v] = nd;
}
}
}
// 在下一层内跑 Dijkstra(此后不再用好石)
dijkstra_layer(nxt);
ans = min(ans, nxt[n]);
cur.swap(nxt);
}
if (ans >= INF / 2) {
cout << "NO\n";
} else {
cout << ans << "\n";
}
return 0;
}
Python
import sys
import heapq
INF = 4 * 10**18
def dijkstra_layer(g, add_stay, dist):
# 层内仅使用“不用好石”的转移:u->v 代价 w + add_stay[v]
n = len(g) - 1
pq = []
vis = [False] * (n + 1)
for i in range(1, n + 1):
if dist[i] < INF:
heapq.heappush(pq, (dist[i], i))
while pq:
du, u = heapq.heappop(pq)
if vis[u]:
continue
vis[u] = True
for v, w in g[u]:
nd = du + w + add_stay[v]
if nd < dist[v]:
dist[v] = nd
heapq.heappush(pq, (nd, v))
def main():
data = sys.stdin.read().strip().split()
it = iter(data)
try:
n = int(next(it)); m = int(next(it)); k = int(next(it))
except StopIteration:
return
a = [0] * (n + 1)
for i in range(1, n + 1):
a[i] = int(next(it))
g = [[] for _ in range(n + 1)]
for _ in range(m):
u = int(next(it)); v = int(next(it)); w = int(next(it))
g[u].append((v, w))
g[v].append((u, w))
add_stay = [0] * (n + 1)
for v in range(1, n + 1):
add_stay[v] = max(0, a[v])
# c=0 层
cur = [INF] * (n + 1)
cur[1] = 0
dijkstra_layer(g, add_stay, cur)
ans = cur[n]
for used in range(k):
# 跨层一次“用好石”的多源初值
nxt = [INF] * (n + 1)
for u in range(1, n + 1):
if cur[u] >= INF:
continue
for v, w in g[u]:
if a[v] < 0:
nd = cur[u] + w + a[v]
if nd < nxt[v]:
nxt[v] = nd
# 在下一层内跑 Dijkstra
dijkstra_layer(g, add_stay, nxt)
ans = min(ans, nxt[n])
cur = nxt
if ans >= INF // 2:
print("NO")
else:
print(ans)
if __name__ == "__main__":
main()
Java
import java.io.*;
import java.util.*;
// 读入加速
class FastScanner {
private final InputStream in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, len = 0;
FastScanner(InputStream is) { in = is; }
private int read() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) return -1;
}
return buffer[ptr++];
}
int nextInt() throws IOException {
int c, sgn = 1, x = 0;
do c = read(); while (c <= ' ' && c != -1);
if (c == '-') { sgn = -1; c = read(); }
while (c > ' ') {
x = x * 10 + (c - '0');
c = read();
}
return x * sgn;
}
}
public class Main {
static final long INF = 4_000_000_000_000_000_000L;
static void dijkstraLayer(List<int[]>[] g, long[] addStay, long[] dist) {
int n = g.length - 1;
boolean[] vis = new boolean[n + 1];
PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[0]));
for (int i = 1; i <= n; i++) {
if (dist[i] < INF) pq.add(new long[]{dist[i], i});
}
while (!pq.isEmpty()) {
long[] top = pq.poll();
long du = top[0];
int u = (int) top[1];
if (vis[u]) continue;
vis[u] = true;
for (int[] e : g[u]) {
int v = e[0], w = e[1];
long nd = du + w + addStay[v];
if (nd < dist[v]) {
dist[v] = nd;
pq.add(new long[]{nd, v});
}
}
}
}
public static void main(String[] args) throws Exception {
FastScanner fs = new FastScanner(System.in);
int n = fs.nextInt(), m = fs.nextInt(), k = fs.nextInt();
long[] a = new long[n + 1];
for (int i = 1; i <= n; i++) a[i] = fs.nextInt();
List<int[]>[] g = new ArrayList[n + 1];
for (int i = 1; i <= n; i++) g[i] = new ArrayList<>();
for (int i = 0; i < m; i++) {
int u = fs.nextInt(), v = fs.nextInt(), w = fs.nextInt();
g[u].add(new int[]{v, w});
g[v].add(new int[]{u, w});
}
long[] addStay = new long[n + 1];
for (int v = 1; v <= n; v++) addStay[v] = Math.max(0L, a[v]);
long[] cur = new long[n + 1];
Arrays.fill(cur, INF);
cur[1] = 0;
dijkstraLayer(g, addStay, cur);
long ans = cur[n];
for (int used = 0; used < k; used++) {
long[] nxt = new long[n + 1];
Arrays.fill(nxt, INF);
for (int u = 1; u <= n; u++) {
if (cur[u] >= INF) continue;
for (int[] e : g[u]) {
int v = e[0], w = e[1];
if (a[v] < 0) {
long nd = cur[u] + w + a[v];
if (nd < nxt[v]) nxt[v] = nd;
}
}
}
dijkstraLayer(g, addStay, nxt);
ans = Math.min(ans, nxt[n]);
cur = nxt;
}
if (ans >= INF / 2) System.out.println("NO");
else System.out.println(ans);
}
}
题目内容
在一个奇幻王国里,有 n 个城市(编号依次为 1 到 n )和 m 条双向道路,第 i 条道路连接城市 ui 和 vi , 基础通行时间为正整数 wi 。此外,王国中每个城市都存在 1个中枢魔法石,每个魔法石有一个能量值,非负能量值的魔法石称之为「坏魔法石」,负数能量值的魔法石称之为「好魔法石」,坏魔法石会增加通行所需时间,好魔法石会减少通行所需时间(增加和减少的时间即为能量值的多少),如果好魔法石足够强力,甚至可以实现时间倒流。
坏魔法石将会强制生效,导致基础通行时间增加,无法被控制。
好魔法石可以控制,选择是否生效,但使用好魔法石的总次数存在限制,第 k 次使用好魔法石生效以后,之后将无法利用任何城市的好魔法石来减少通行时间。换句话说,单次行程中好法石总使用次数不超过 k 次。
魔法石不会消失,可以多次使用。
当你从城市 u 前往城市 v 时,路径的实际通行时间计算如下:
通行时间=城市 u 到城市 v 的道路基础通行时间加上城市 v 生效的魔法石能量值。
请计算从城市 1 到城市 n 的最小实际通行时间,注意,您可以重复经过城市和道路。特别地,如果无论如何都无法到达城市 n ,直接输出 NO 。
输入描述
第一行输入三个整数 n,m,k(2≤n≤103;1≤m≤min{2×103,2n×(n−1)};1≤k≤103)
第二行输入 n 个整数 a1,a2,...,an(−105≤ai≤105),其中 ai 表示第 i 个城市的魔法石能量。
接下来 m 行,第 i 行输入三个整数 ui,vi,wi(1≤ui,vi≤n;ui=vi;1≤wi≤105) ,表示城市 ui 与城市 vi 之间存在一条同行时间为 wi 的路径。除此之外,保证任意两个城市之间至多存在一条道路。
注意,本题不保证图的连通性,即可能存在两个城市之间无法通过任何路径互相到达的情况。
输出描述
如果无论如何都无法到达城市 n ,直接输出下 NO ,否则输出一个整数,表示从城市 1 到城市 n 的最少实际通行时间。
样例1
输入
5 5 2
0 0 0 -10 0
1 2 1
2 3 1
3 5 1
1 4 6
4 5 1
输出
-13
说明
在这个样例中,唯一的最优走法是 1→2→3→5→4→5→4→5,实际通行时间为 1+1+1+(1−10)+1+(1−10)+1 。