P14923 [GESP202512 八级] 猫和老鼠
猫和老鼠所在的庄园可以视为一张由 n 个点和 m 条带权无向边构成的连通图。结点依次以 1, 2, …, n 编号,结点 i(1 ≤ i ≤ n)有价值为 ci 的奶酪。在 m 条带权无向边中,第 i(1 ≤ i ≤ m)条无向边连接结点 ui 与结点 vi,边权 wi 表示猫和老鼠通过这条边所需的时间。
猫窝位于结点 a,老鼠洞位于结点 b。对于老鼠而言,结点 u 是安全的当且仅当:
- 老鼠能规划一条从结点 u 出发逃往老鼠洞的路径,使得对于路径上任意结点 x(包括结点 u 与老鼠洞)都有:猫从猫窝出发到结点 x 的最短时间严格大于老鼠从结点 u 沿这条路径前往结点 x 所需的时间。
老鼠在拿取安全结点的奶酪时不存在被猫抓住的可能,但在拿取不是安全结点的奶酪时则不一定。为了确保万无一失,老鼠决定只拿取安全结点放置的奶酪。请你计算老鼠所能拿到的奶酪价值之和。
输入格式
第一行,两个正整数 n, m,分别表示图的结点数与边数。
第二行,两个正整数 a, b,分别表示猫窝的结点编号,以及老鼠洞的结点编号。
第三行,n 个正整数 c1, c2, …, cn,表示各个结点的奶酪价值。
接下来 m 行中的第 i 行(1 ≤ i ≤ m)包含三个正整数 ui, vi, wi,表示图中连接结点 ui 与结点 vi 的边,边权为 wi。
输出格式
输出一行,一个整数,表示老鼠所能拿到的奶酪价值之和。
输入输出样例 #1
输入 #1
1 | 5 5 |
输出 #1
1 | 22 |
输入输出样例 #2
输入 #2
1 | 6 10 |
输出 #2
1 | 3 |
说明/提示
对于 40% 的测试点,保证 1 ≤ n ≤ 500,1 ≤ m ≤ 500。
对于所有测试点,保证 1 ≤ n ≤ 105,1 ≤ m ≤ 105,1 ≤ a, b ≤ n 且 a ≠ b,1 ≤ ui, vi ≤ n,1 ≤ wi ≤ 109。
注:GESP 原题缺失 ci 范围,可按 1 ≤ ci ≤ 109 计算。
题解
这是一个经典的图论问题,可以转化为最短路问题的变种。我们需要计算每个节点是否满足特定的“安全条件”。
核心思路解析
这个问题包含两个核心部分:
- 猫的行动:猫从 a 点出发,到任意点 x 的最短时间是固定的。我们可以用 Dijkstra 算法求出猫到所有点 x 的最短时间,记为 Dcat[x]。
- Jerry 的规划:Jerry 需要寻找一条从 u 到 b(老鼠洞)的路径,使得路径上任意一点 x,满足 Dcat[x] > TimeJerry(u → x)。
逆向思维推导
与其思考“Jerry 从 u 走到 b”,不如从老鼠洞 b 反向推导回到 u。
定义 S[u] 为:Jerry 在节点 u 时,相对于猫到达该点的时间,他拥有的最大“时间富余量”(Slack)。或者更准确地说,这是 Jerry 在 u 点可以“可以延迟多久出发”还能安全到达洞口的安全阈值。
根据题目定义,对于路径上的点 x(从 u 到 b),必须满足:
TimeJerry(u → x) < Dcat[x]
假设 Jerry 在 u 点耗费了 t 时间出发(虽然题目是从 0 开始,但为了递推,我们引入这个相对量),则到达 x 点的时间为 t + dist(u, x)。
安全条件变为:
t + dist(u, x) < Dcat[x] ⟹ t < Dcat[x] − dist(u, x)
这意味着,为了保证路径后续所有点都安全,Jerry 在 u 点的“最大容忍时间” S[u] 受限于路径上所有点的瓶颈。
递推公式(状态转移):
我们从老鼠洞 b 开始倒推。
基准情况:在老鼠洞 b,只要猫还没抓到他就是安全的。所以 S[b] = Dcat[b]。
递推:假设我们已经知道节点 v 的安全余量 S[v],现在有一条边 (u, v) 权重为 w(Jerry 实际上是 u → v)。
Jerry 在 u 点的安全受到两个限制:
- u 点本身必须安全:时间必须小于 Dcat[u]。
- 通过 v 后续的路径必须安全:Jerry 从 u 到 v 走了 w 时间,到达 v 后,他剩余的“时间余额”变成了 S[v]。所以在 u 点,他相对于 v 的限制是 S[v] − w。
综上,从 v 推导到 u 的状态转移方程为:
S[u] = max (S[u], min (Dcat[u], S[v] − w))
我们需要求出每个点的最大 S[u]。这本质上是一个最大瓶颈路(Maximum Bottleneck Path)问题,可以使用类似 Dijkstra 的贪心算法(使用大顶堆)来解决。
判定标准:
如果计算出的 S[u] > 0,说明 Jerry 在 t = 0 时刻出发是满足严格小于猫的时间的,该节点安全。
算法步骤
- 计算猫的最短路:以 a 为起点运行标准 Dijkstra,求出所有点的 Dcat[i]。
- 计算安全余量:
- 初始化所有点的 S[i] = −1。
- 设 S[b] = Dcat[b],将 (S[b], b) 加入大顶堆优先队列。
- 运行“最大瓶颈路”算法:
- 每次取出当前 S 值最大的点 v。
- 遍历邻居 u(边权 w):
- 计算通过 v 能够给 u 带来的余量:limit = min (Dcat[u], S[v] − w)。
- 如果 limit > S[u],则更新 S[u] = limit,并将 u 入队。
- 统计结果:遍历所有节点 i,如果 S[i] > 0,则将 ci 加入总奶酪数。
C++ 示范代码
1 |
|
样例说明
输入样例
Plaintext
1 | 5 5 1 5 |
样例解释
- 图结构:一条链 1 − 2 − 3 − 4 − 5,加上一条边 2 − 5。
- 位置:猫在 1,老鼠洞在 5。
- 奶酪:c1 = 10, c2 = 20, c3 = 30, c4 = 40, c5 = 50。
- 猫的到达时间 (Dcat):
- 1: 0 (起点)
- 2: 10
- 3: 20
- 4: 30
- 5: 40 (路径 1-2-3-4-5) 或 45 (路径 1-2-5)。最短是 40。
- Jerry 的安全余量 (Slack)
计算(从 5 号点倒推):
- Node 5: Slack[5] = Dcat[5] = 40。
- 推导 Node 4 (边权 10): min (Dcat[4], Slack[5] − 10) = min (30, 30) = 30。
- 推导 Node 3 (边权 10): min (Dcat[3], Slack[4] − 10) = min (20, 20) = 20。
- 推导 Node 2 (来自 3, 边权 10): min (Dcat[2], Slack[3] − 10) = min (10, 10) = 10。
- 推导 Node 2 (来自 5, 边权 35): min (10, 40 − 35) = 5。最大值取 10。
- 推导 Node 1 (边权 10): min (Dcat[1], Slack[2] − 10) = min (0, 0) = 0。
- 判定:
- Node 5: Slack = 40 > 0 (安全)
- Node 4: Slack = 30 > 0 (安全)
- Node 3: Slack = 20 > 0 (安全)
- Node 2: Slack = 10 > 0 (安全)
- Node 1: Slack = 0 (不安全,必须严格大于)
- 结果:20 + 30 + 40 + 50 = 140。
输出
Plaintext
1 | 140 |