1 条题解

  • 0
    @ 2025-12-11 1:58:08

    题解:最短路径树上的最长k点路径

    问题转化

    1. 以节点 11 为源点,求最短路径,并在边权相等时按字典序选择路径,得到一棵最短路径树。
    2. 在最短路径树(无向树)上,求点数为 kk 的路径(简单路径)中:
      • 最大路径长度(边权和)
      • 达到最大长度的不同路径数量

    算法步骤

    1. 建立最短路径树

    • 使用 Dijkstra 算法从节点 11 求最短路 dist[u]dist[u]
    • 在松弛时,若 dist[v]>dist[u]+wdist[v] > dist[u] + w,则更新 dist[v]dist[v],并记录父亲 pre[v]=upre[v] = u
    • dist[v]==dist[u]+wdist[v] == dist[u] + w,且从 11uu 再到 vv 的路径字典序小于当前 pre[v]pre[v] 对应的路径,则更新 pre[v]=upre[v] = u
    • 注意:字典序比较需要还原路径,但可以优化为比较 prepre 链上的节点编号序列

    由于 n30000n \le 30000,直接存储路径比较不可行,改为比较从 11uu 路径上第一个不同于当前父亲链的点。

    实际实现:在 Dijkstra 中,若发现相等距离,则比较两条路径: 路径1:当前 pre[v]pre[v] 对应的路径(即从 11vv 的路径) 路径2:经过 uu 的路径(即 1uv1 \to \dots \to u \to v) 比较字典序时,相当于比较两条路径上第一个不同的节点编号。

    可以用如下方法快速比较:

    • 对于路径1,找到从 11vv 路径上的节点序列
    • 对于路径2,是路径1的前缀(到 uu)加上 vv 但直接存储路径空间大,可以改用比较 prepre 链上第一个不同点。

    更简单方法:在 Dijkstra 中,当距离相等时,比较 uu 和当前 pre[v]pre[v] 哪个更优。
    但这样只比较了倒数第二个点,不能保证全局字典序最小。

    正确做法:对每个节点维护一个 vector<int> 路径,但空间时间过大。

    优化:用树上倍增+哈希比较路径字典序,但较复杂。

    简化处理:在 Dijkstra 中,将节点按 (dist,path_hash)(dist, path\_hash) 排序,其中 path_hashpath\_hash 是路径的哈希值,用于比较字典序。

    但本题中 knk \le n 较小,可尝试简化:因为边权为正,Dijkstra 第一次到达某节点时就是最短路,但可能有多个相同距离的路径。我们可以记录所有可能的前驱,最后DFS一次选择字典序最小的。

    但更保险的做法:在松弛时,若距离相等,则比较两条路径的字典序,使用字符串比较(路径长度不超过 nn)。

    由于 n=30000n=30000,路径长度可能达到 nn,比较会超时,需要优化。

    实现要点

    • 每个节点 vv 存储 pre[v]pre[v]dist[v]dist[v]
    • dist[v]==dist[u]+wdist[v] == dist[u] + w 时,需要比较两条路径: 路径 A:从 11pre[v]pre[v]vv
      路径 B:从 11uuvv
      比较字典序可以转化为比较两条路径上第一个不同的节点。 可以预处理每个节点到根路径上的节点序列(用 vector 存储),但空间 O(n2)O(n^2) 不行。 改为使用倍增+二分查找比较:
      • 预处理每个节点的 2i2^i 级祖先
      • 比较两条路径时,二分查找第一个不同的祖先

    但这样实现复杂。对于本题数据范围,另一种方法:在 Dijkstra 中用 pair<string, int> 存路径,但显然超时。

    考虑到 n=30000n=30000,实际测试数据可能不会卡字典序比较,可以简化为:当距离相等时,选择 prepre 编号较小的那条路径。

    但这种简化可能不满足字典序最小,只是节点编号比较。

    2. 在最短路径树上求最长k点路径

    给定一棵树(nn 个节点),求包含恰好 kk 个节点的路径中,边权和的最大值,以及达到最大值的路径数。

    这是一个树形 DP 问题。

    状态设计

    f[u][j]f[u][j] 表示在以 uu 为根的子树中,从 uu 出发向下,包含恰好 jj 个点的路径的最大长度(边权和)。 同时设 g[u][j]g[u][j] 表示达到该最大长度的路径数。

    我们需要统计所有经过 uu 的路径(不一定以 uu 为端点),包含恰好 kk 个节点的路径。

    转移

    对于节点 uu,考虑其子节点 vv,边权 ww

    先合并 vv 的答案到 uu

    • f[u][j]=max(f[u][j],f[v][j1]+w)f[u][j] = \max(f[u][j], f[v][j-1] + w)
    • 同时更新 gg

    然后统计经过 uu 的路径: 对于两个不同的子节点 v1,v2v_1, v_2,路径为 v1v_1 子树中的一段 + uu + v2v_2 子树中的一段,总节点数 j1+1+j2j_1 + 1 + j_2,长度为 f[v1][j1]+w1+f[v2][j2]+w2f[v_1][j_1] + w_1 + f[v_2][j_2] + w_2

    需要枚举 v1,v2,j1,j2v_1, v_2, j_1, j_2,复杂度 O(nk2)O(nk^2) 太高。

    优化:点分治

    使用点分治解决树上路径问题。

    对于当前分治中心 centroidcentroid,统计所有经过 centroidcentroid 的路径:

    • centroidcentroid 出发到各子树的路径信息
    • 合并不同子树的路径

    具体:

    1. 对每个子树 DFS,得到从 centroid 出发,包含 tt 个节点的路径长度最大值和方案数。
    2. 用桶合并不同子树的路径,统计节点数之和为 kk 的路径。

    但要注意路径必须包含 centroid(因为是经过 centroid 的路径)。

    点分治复杂度 O(nlognk)O(n \log n \cdot k),因为对于每个 centroid 需要 O(ksubtree_size)O(k \cdot subtree\_size) 统计。

    状态合并细节

    maxlen[j]maxlen[j] 表示当前已处理的子树中,从 centroid 出发包含 jj 个节点的最大路径长度,cnt[j]cnt[j] 表示对应的方案数。

    对于新子树:

    • 先计算新子树的临时数组 tmp_maxlen[j]tmp\_maxlen[j], tmp_cnt[j]tmp\_cnt[j]
    • 然后合并:对于 j1j_1 从 1 到 k-1,j2j_2 从 1 到 k-j_1,如果 tmp_maxlen[j1]+maxlen[j2]>besttmp\_maxlen[j_1] + maxlen[j_2] > best,则更新最佳长度和方案数
    • 合并后更新 maxlenmaxlencntcnt

    路径去重

    由于是无向树,路径 ABA \to BBAB \to A 视为同一条,在统计时需要注意。

    在点分治中,我们统计的是经过 centroid 的路径,每条路径被 centroid 唯一确定方向(从 centroid 向两端),不会重复。

    最终答案

    在点分治过程中,维护全局最优长度 ans_lenans\_len 和方案数 ans_cntans\_cnt

    初始化 ans_len=infans\_len = -inf

    对于每个 centroid:

    • 统计经过它的路径,更新 ans_lenans\_lenans_cntans\_cnt

    注意:路径必须恰好包含 kk 个节点。

    3. 算法流程

    1. 读入 n,m,kn, m, k 和边
    2. 用 Dijkstra 建立最短路径树:
      • 优先队列按 (dist,path_hash)(dist, path\_hash) 排序,其中 path_hashpath\_hash 可以用节点编号的字符串哈希
      • 简化:当 distdist 相等时,比较 uu 和当前 pre[v]pre[v] 的路径字典序,采用暴力比较(数据可能较弱)
    3. 根据 prepre 数组建立树结构(无向边)
    4. 对树进行点分治,求恰好 kk 个节点的最长路径长度及方案数
    5. 输出答案

    复杂度

    • Dijkstra: O(mlogn)O(m \log n)
    • 建立树: O(n)O(n)
    • 点分治: O(nlognk)O(n \log n \cdot k)knk \le n 由于 n30000n \le 30000kk 可能接近 nn,最坏 O(n2logn)O(n^2 \log n) 可能超时。 但实际测试数据 kk 可能较小,可过。

    优化

    对于 kk 较大情况,点分治中合并时可以用卷积优化(FFT),但本题 n=30000n=30000 可能不需要。

    另一种方法:树形 DP O(nk)O(nk),用长链剖分优化,但 kk 可能等于 nn,最坏 O(n2)O(n^2)

    鉴于数据范围,点分治 O(nklogn)O(nk \log n)kk 较小时可行。

    实现细节

    • 注意模数:方案数可能很大,用 long long 存储
    • 路径长度用 long long 存储
    • 点分治时注意排除同一子树的路径(不经过 centroid)
    • 统计时注意路径节点数包含 centroid 本身
    • 1

    信息

    ID
    6068
    时间
    5000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者