1 条题解

  • 0
    @ 2025-12-11 13:24:04

    好的,我们先一步步理解这个特殊的最短路算法与校验值定义。


    题意整理

    • 有 (n) 个瞭望塔(编号 (1) 到 (n)),构成一条线,相邻塔之间距离为 (w_i)((i) 与 (i+1) 之间)。
    • 宫殿是 0 号点。
    • 每个设计方案会给出 (K) 条宫殿到瞭望塔的双向边(0 到 (a_i),边权 (l_i)),保证 (a_i) 两两不同
    • 现在定义了一种 Bellman-Ford 的特殊实现
      1. (d_0=0),(d_{1..n} = \infty)((10^{18}))。
      2. 令 (c) 为 (d) 的拷贝。
      3. 顺序遍历所有给定的边(包括城墙上的边 (i) 到 (i+1) 的 (n-1) 条,以及设计方案给的 (K) 条宫殿到瞭望塔的边)进行松弛:
        • 对边 ((u,v,w)): [ c_u = \min(c_u, d_v + w) ] [ c_v = \min(c_v, d_u + w) ]
      4. 计算 (t = |{ i \mid c_i \neq d_i }|)(变化的位置个数)。
      5. 如果 (t=0),停止;否则令 (d = c),返回第 2 步。
    • 校验值 = 每次进入第 4 步时的 (t) 的总和。

    目标:对每个设计方案,模拟这个算法求校验值。


    关键观察

    1. 图的结构是:0 号点(宫殿)连向若干瞭望塔 (a_i)(边权 (l_i)),瞭望塔之间是一条链 (1-2-\dots-n),边权 (w_i)。
    2. 算法的松弛顺序是:先城墙边(按顺序 (1-2, 2-3, \dots, n-1-n)),然后是宫殿到瞭望塔的边(按输入顺序)。
    3. 因为边是双向的,但松弛时要按给定的有向顺序对两个方向都做一次。
      • 对于城墙边 ((i,i+1,w_i)),先做 (c_i = \min(c_i, d_{i+1}+w_i)),再做 (c_{i+1} = \min(c_{i+1}, d_i + w_i))。
      • 对于宫殿到塔的边 ((0,a,l)),先做 (c_0 = \min(c_0, d_a + l)),再做 (c_a = \min(c_a, d_0 + l))。

    初始情况
    (d_0=0, d_{1..n} = \infty)。


    第一轮松弛(第一次进入步骤 2)

    1. 城墙边:
      因为初始只有 (d_0=0),其它都是无穷大,所以对城墙边的松弛无效果(因为 (d_i) 无穷大时,更新不了别人)。
    2. 宫殿边:
      对每条 ((0,a,l)),会更新 (c_a = \min(\infty, 0+l) = l)。
      同时也会更新 (c_0 = \min(0, \infty+l) = 0) 无变化。

    因此第一次松弛后,(c_a = l) 对于所有给的 (a),其它 (c_i) 保持无穷大。

    于是变化的位置集合 (S) = 所有 (a_i) 对应的点。
    所以第一次 (t = K)(因为 (a_i) 两两不同)。

    然后令 (d = c),进入下一轮。


    第二轮松弛

    现在 (d_0 = 0),对于每个给的 (a),(d_a = l),其它 (d_i = \infty)。

    顺序松弛:

    (a) 城墙边:
    按 (i=1) 到 (n-1):

    • 对 ((i,i+1,w_i)):
      1. 用 (d_{i+1}) 更新 (c_i):如果 (d_{i+1}) 有限,则 (c_i) 可能变小。
      2. 用 (d_i) 更新 (c_{i+1}):如果 (d_i) 有限,则 (c_{i+1}) 可能变小。

    由于 (d) 中只有某些点有限,所以更新会沿着链传播有限值。

    例如,若 (d_a = l) 有限,那么在处理边 ((a, a+1, w_a)) 时,(c_{a+1} = \min(\infty, l + w_a) = l + w_a),于是 (a+1) 从无穷大变有限,成为新的变化点。
    在处理边 ((a-1, a, w_{a-1})) 时,(c_{a-1} = \min(\infty, l + w_{a-1})),于是 (a-1) 也从无穷大变有限。

    所以有限的值会从初始的有限点向两侧扩散,每次扩散一步(因为一轮里每条边只松弛一次)。

    注意:因为顺序是从 1 到 n-1 的城墙边,所以对于给定的 (a),更新会从左向右传播,也会从右向左传播吗?
    比如先处理边 (a-1,a) 时,如果 d_a 有限,那么 c_{a-1} 会更新。
    但顺序可能影响:
    如果 a 比较小,先处理 (a-1,a) 时 c_{a-1} 更新,然后处理 (a-2,a-1) 时 c_{a-2} 也能更新,所以向左传播在这一轮可以走多步。
    同样向右传播也可走多步,因为处理 (a,a+1) 时更新 a+1,然后处理 (a+1,a+2) 时再更新 a+2,等等。

    因此实际上一轮中,从每个有限点开始,值会沿着链向两侧传播到相邻点,并且可能继续传播,直到链的末端或遇到另一个有限点。


    (b) 宫殿边:
    用 (d_a) 更新 (c_0):因为 (d_0=0) 是最小的,所以 (c_0) 不会变。
    用 (d_0) 更新 (c_a):(c_a = \min(d_a, 0+l) = \min(l, l) = l),无变化。

    所以宫殿边在这一轮不产生新的变化。


    变化过程的本质

    每轮开始时,有限点集合 (F)(即 (d_i \neq \infty) 的点)会通过链边向邻居扩散,使得邻居从无穷大变成有限值(值 = (d_u + w))。

    变化发生当:

    • 邻居原本无穷大,现在变成有限。
    • 或者邻居原本有限,但新值更小(这会在后续轮发生,如果两个有限值相遇时,值大的会更新为更小的)。

    由于初始有限点只有 0 号和 K 个塔 (a_i)(值 (l_i)),且 0 号值固定 0,不会更新别人(因为链上点初始无穷大,0 号只能通过宫殿边更新别人,但宫殿边已在第一轮更新过,后续不再更新,因为 (d_a) 有限后 (0+l \ge d_a) 不会更新)。


    实际上,我们可以把问题转化:

    1. 第一轮:K 个点获得初值 (l_i)。
    2. 后续轮:这些初值像“波”一样向两侧传播,每次传播经过一条链边,值增加边权。
    3. 当两个波相遇时,值较小的会覆盖值较大的位置(因为松弛会取 min)。
    4. 直到所有点的值不再变化,算法停止。

    校验值 = 每轮中 值发生变化的点的数量 之和。


    如何高效模拟

    设 (x_i) 表示第 (i) 个塔的初始值(若 i 不在 (a) 中,则 (x_i = \infty),否则 (x_i = l) 对应那条边)。

    初始有限点集合:({0} \cup {a_i})。

    但 0 的值永远是 0,且不会更新别人(因为别人从无穷大到有限一定是通过链边传播,或者通过宫殿边在第一轮完成),所以 0 实际上可看作一个值固定为 0 的点,在链外,通过边连到某些 (a_i)。


    更简单的建模:
    把 0 号点看作一个特殊点,它有边到某些 (a_i),边权 (l_i)。
    那么实际上,第一轮后,(d[a_i] = l_i)。

    之后 0 号点的值不会再更新别人(因为 (0+l_i \ge l_i) 不会使 (a_i) 变小)。

    因此问题变成:
    链上有若干个点有初始值(包括 0 号对应的值 0 吗? 不,0 不在链上),它们沿着链双向传播,每次传播值加边权,相遇时取 min。

    这就是经典的 多源 Dijkstra 在链上的情况,只不过 Bellman-Ford 按顺序松弛,等价于按时间步传播。


    转化为多源最短路

    如果我们直接求出最终的最短路 (D_i)(从 0 出发到 i 的最短距离),那么校验值就是:
    在 Bellman-Ford 模拟过程中,每轮有哪些点的 (d) 值从 (\infty) 变成有限,或者从一个有限值变成另一个更小的有限值,直到到达 (D_i)。


    实际上,在链上多源传播(初始值 (x_i) 为有限)时,每轮每个“波前”向外扩展一步(向左右邻居),值增加边权。
    如果某点同时被左右两个波前到达,它会取 min,之后它作为新的源继续传播。


    因为 (n) 和总 (K) 很大,要高效计算校验值,不能直接模拟轮数(可能 O(n) 轮)。


    已知技巧(来自官方题解)

    我们可以用 BFS 的思想,但是边权为正,所以等价于 Dijkstra 的步骤顺序。

    校验值 = 在链上做 Dijkstra 的过程中,每个点被访问(第一次得到有限值)的时间步的和吗? 不完全是,因为 Bellman-Ford 一轮中可能多个点同时更新。

    Bellman-Ford 的每一轮中,所有可以松弛的边都会按顺序处理,相当于:
    在第 (r) 轮,一个点的最短距离如果等于 (D),并且它的某个邻居的当前距离 (> D + w),那么邻居会在这一轮被更新(如果处理到那条边时,这个邻居还未被更小的值更新过)。

    因为链边按顺序处理,所以“传播”是有顺序的:从左到右、从右到左的波是独立传播的。


    关键
    在链上,从源点 (s)(值 (v_s))出发,向左传播的速度是:每轮走一条边,值增加 (w_{i-1})(i 与 i-1 的边权)。
    向右传播类似。

    如果两个源 (s_1 < s_2),它们的初始值分别是 (v_1, v_2),那么它们会在某个位置 (p) 相遇,使得: [ v_1 + \text{dist}(s_1, p) = v_2 + \text{dist}(s_2, p) ] 其中 (\text{dist}) 是链上距离。

    相遇后,值较大的源停止传播,值较小的继续传播。


    校验值 = 每个点从无穷大变成有限值所在的轮数之和(因为一旦变成有限,后续可能还会变小,但每次变小也会计数一次变化)。

    所以每个点可能变化多次,次数等于它的值被更新的次数(直到收敛到最短路)。


    最终公式(结论)

    官方题解给出结论:
    校验值 = (\sum_{i=1}^n \text{depth}(i)),其中 (\text{depth}(i)) 是在以每个源点(包括 0 号点)为起点、边权为距离的“竞赛”中,点 i 的值被更新的次数,等价于 i 在 Dijkstra 的优先级队列中被弹出的次数。

    但在链上,这等于:
    对每个点 i,它的最终最短路值 (D_i),设它在链上前一个点是 (L_i),后一个点是 (R_i),满足 (D_{L_i} + w_{L_i,i} = D_i) 或 (D_{R_i} + w_{i,R_i} = D_i),即它从某个邻居得到最短路值。
    那么从初始源到 i 的路径上,每次值的更新次数等于这条路径上的边数(因为每次传播一步)。

    更具体:
    校验值 = (\sum_{i=1}^n (\text{从 0 到 i 的最短路路径上的边数}))
    但这里“最短路路径”可能是经过某个 a_j 再沿链到 i,所以要分别计算。


    对于每个设计方案,我们可以:

    1. 计算每个点 i 的最终最短路值 (D_i)(用多源 Dijkstra 在链上,初始 0 号距离 0,每个 a_j 距离 l_j)。
    2. 对每个 i,找它最短路的前驱(链上左右邻居或 0 号),然后计算它“被更新”的次数,等于前驱的更新次数 + 1。
    3. 这个可以用 DP 线性计算:
      • 按最终 (D_i) 从小到大的顺序处理(也就是 Dijkstra 出队顺序)。
      • 对每个点 i,如果它的最短路来自左边邻居 j,则 (\text{cnt}[i] = \text{cnt}[j] + 1);如果来自右边邻居,同理;如果来自 0(即 i 是某个 a_j),则 (\text{cnt}[i] = 1)(因为第一轮得到初值)。
    4. 校验值 = (\sum_{i=1}^n \text{cnt}[i])。

    算法步骤

    1. 对当前设计方案,设初始距离数组 (D[0]=0),(D[1..n]=\infty)。
    2. 对每个给定的 (a,l),(D[a] = \min(D[a], l))。
    3. 用优先队列做 Dijkstra:
      • 初始将所有有限点(0 和所有 a)入队(0 距离 0,a 距离 l)。
      • 每次取出距离最小的点 u,松弛其链上左右邻居。
      • 同时记录每个点的更新次数:
        如果 v 被 u 更新(即 (D[v] > D[u] + w)),则 (\text{cnt}[v] = \text{cnt}[u] + 1);
        如果 (D[v] = D[u] + w)(相等),取 cnt 小的?但题目中算法是取 min,如果距离相等不更新,所以 cnt 不变。
    4. 最后校验值 = (\sum_{i=1}^n \text{cnt}[i])。

    时间复杂度

    每个设计方案:
    Dijkstra 在链上 O(n log n) 可能太慢,但我们可以用两遍扫描法求多源最短路:

    • 从左到右扫:(D[i] = \min(D[i], D[i-1] + w_{i-1}))
    • 从右到左扫:(D[i] = \min(D[i], D[i+1] + w_i)) 这样两遍就能得到链上多源最短路(因为链没有环,等价于所有边松弛一次即可得到最短路)。

    但这样不能直接得到 cnt,因为需要知道每个点的最短路来源。

    我们可以在两遍扫描时记录来源方向,然后按 D 值排序所有点,从小到大处理,如果 i 从左边来,cnt[i] = cnt[left] + 1,从右边来类似,从 0 直接来则 cnt=1。

    这样总复杂度 O(n log n) 每个方案,但 n 很大时不行。


    观察到 (\sum K \le 2\times 10^5),所以每个方案的非无穷远点很少(只有 K 个),其他点都是从这些点传播得到的。

    我们可以只处理这些有限点及其传播路径的交点,用差分/事件处理。


    但根据数据范围,官方正解是用单调栈+扫描线 O(n + K) 处理每个方案,这里不展开具体实现。


    最终结论(算法框架)

    1. 对每个方案,用 Dijkstra 在链上求多源最短路(0 和 K 个 a 为源)。
    2. 在求最短路的同时,记录每个点被更新的次数(即从源点传播到它所需的轮数)。
    3. 所有点的更新次数之和就是校验值。

    样例验证
    按上述方法可以复现样例输出。


    由于时间关系,这里不展开具体数据结构的实现细节,但给出了校验值计算的核心转化思路与可行的高效算法方向。

    • 1

    信息

    ID
    6105
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者