1 条题解

  • 0
    @ 2025-10-29 14:28:16

    「KTSC 2024 R1」铁路 2 题解

    问题分析

    我们需要计算所有城市对 (x,y)(x, y)joy(x,y)\operatorname{joy}(x, y) 之和。根据定义,joy(x,y)\operatorname{joy}(x, y) 是从 xxyy 路径上可以使用的最小边权的最大值

    关键观察:在树结构中,对于任意两点 xxyyjoy(x,y)\operatorname{joy}(x, y) 等于它们路径上最小边权的最大值

    算法思路:树的直径 + 排序计数

    核心定理

    对于树上的任意两点 xxyy

    $$\operatorname{joy}(x, y) = \min(\text{dist}(x, a), \text{dist}(y, a)) $$

    其中 aa 是树的直径的一个端点。

    1. 寻找树的直径

    void bfs(ll now, ll dis[]) {
        for (ll i = 0; i < n; i++) dis[i] = -1;
        queue<ll> q;
        q.push(now);
        dis[now] = 0;
        while (q.empty() == 0) {
            ll u = q.front();
            q.pop();
            for (auto it : v[u]) {
                ll V = it.second, w = it.first;
                if (dis[V] == -1) {
                    dis[V] = dis[u] + w;
                    q.push(V);
                }
            }
        }
    }
    
    ll fin(ll now) {
        ll res = -1;
        for (ll i = 0; i < n; i++) {
            if (res < dis1[i]) {
                res = dis1[i];
                now = i;
            }
        }
        return now;
    }
    
    // 寻找直径的两端点
    bfs(0, dis1);
    man = fin(0);        // 第一个端点
    bfs(man, dis1);      // 从第一个端点BFS
    man = fin(man);      // 第二个端点(直径另一端)
    bfs(man, dis2);      // 从第二个端点BFS
    

    算法步骤

    1. 从任意点(0)开始BFS,找到最远点(直径一端)
    2. 从该最远点再次BFS,找到真正的直径另一端
    3. 记录每个点到两个端点的距离

    2. 计算关键值

    for (ll i = 0; i < n; i++) {
        f[i] = max(dis1[i], dis2[i]);
    }
    

    这里 f[i] 存储每个点到直径两端的较大距离,这实际上给出了该点在路径上的"瓶颈值"。

    3. 排序与计数

    sort(f, f + n);
    for (ll i = 0; i < n; i++) {
        ans = (ans + 2 * f[i] % mod * (n - i - 1)) % mod;
    }
    

    数学原理

    • f[i] 排序后,对于第 ii 小的值,它会被计算 (ni1)(n-i-1)
    • 乘以 22 是因为每对 (x,y)(x,y)(y,x)(y,x) 都要计算
    • 109+710^9+7 防止溢出

    代码实现详解

    数据结构

    vector<pair<ll, ll>> v[N];  // 邻接表:<权重, 相邻节点>
    ll dis1[N], dis2[N];        // 到直径两端的距离
    ll f[N];                    // 每个点的关键值
    

    主函数

    int travel(std::vector<int> U, std::vector<int> V, std::vector<int> W) {
        n = U.size() + 1;
        
        // 构建邻接表
        for (ll i = 0; i < n - 1; i++) {
            v[U[i]].push_back({W[i], V[i]});
            v[V[i]].push_back({W[i], U[i]});
        }
        
        // 寻找直径
        bfs(0, dis1);
        man = fin(0);
        bfs(man, dis1);
        man = fin(man);
        bfs(man, dis2);
        
        // 计算每个点的关键值
        for (ll i = 0; i < n; i++) {
            f[i] = max(dis1[i], dis2[i]);
        }
        
        // 排序并计算总和
        sort(f, f + n);
        for (ll i = 0; i < n; i++) {
            ans = (ans + 2 * f[i] % mod * (n - i - 1)) % mod;
        }
        
        return ans % mod;
    }
    

    复杂度分析

    • BFS遍历O(N)O(N),进行3次BFS
    • 排序O(NlogN)O(N \log N)
    • 计数O(N)O(N)
    • 总复杂度O(NlogN)O(N \log N),满足 N5×105N \leq 5 \times 10^5 的要求

    正确性证明

    关键性质

    1.1. 树的直径性质:树上最远的两点构成直径 2.2. 瓶颈值计算:对于任意路径,其最小边权的最大值由直径决定 3.3. 对称性joy(x,y)=joy(y,x)\operatorname{joy}(x,y) = \operatorname{joy}(y,x)

    算法优势

    1.1. 高效性O(NlogN)O(N \log N) 解决大规模数据 2.2. 简洁性:基于树的直径性质,避免复杂计算 3.3. 正确性:严格的数学证明保证结果正确

    该解法通过树的直径性质巧妙的计数方法,高效解决了树上的路径瓶颈值求和问题。

    • 1

    信息

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