UVa 1664 Conquer a New Region

题目描述

历史的车轮不断前进,国王在遥远的大陆上征服了一个新的区域。这个区域包含 NNN 个城镇(编号从 111NNN),由若干条道路连接。可以确认任意两个城镇之间只有唯一的一条路径。

我们定义道路 (i,j)(i, j)(i,j) 的容量 C(i,j)C(i, j)C(i,j) 表示这两个城镇之间的道路最多允许运输 C(i,j)C(i, j)C(i,j) 单位的货物。对于城镇 iiijjj 之间的路径,我们定义 S(i,j)S(i, j)S(i,j) 为这条路径上的最大交通容量,其值等于路径上所有道路容量的最小值。

国王希望选择一个中心城镇来储存战争资源,使得从该中心到其他 N−1N-1N1 个城镇的总交通容量最大化。你的任务是帮助国王找到这个中心,并输出最大的总交通容量。

输入格式

  • 多个测试用例
  • 每个测试用例第一行包含整数 NNN (1≤N≤200,0001 \leq N \leq 200,0001N200,000)
  • 接下来 N−1N-1N1 行,每行包含三个整数 a,b,ca, b, ca,b,c,表示城镇 aaabbb 之间有一条容量为 ccc 的道路

输出格式

对于每个测试用例,输出一个整数表示所选中心城镇的最大总交通容量。

题目分析

问题本质

这是一个在树结构上的优化问题:

  • 给定一棵 NNN 个节点的树,每条边有一个容量值
  • 对于任意两个节点 iiijjj,它们之间的交通容量 S(i,j)S(i, j)S(i,j) 是路径上所有边容量的最小值
  • 需要找到一个中心节点 centercentercenter,使得 ∑j=1NS(center,j)\sum_{j=1}^{N} S(center, j)j=1NS(center,j) 最大

关键观察

  1. 树结构特性:任意两点间只有唯一路径,这简化了问题
  2. 瓶颈性质S(i,j)S(i, j)S(i,j) 由路径上的最小容量边决定
  3. 贪心思想:容量大的边应该尽可能多地被利用

解题思路

本题的巧妙解法是使用并查集配合类似 Kruskal\texttt{Kruskal}Kruskal 算法的贪心策略

  1. 逆向思维:不从中心点出发计算到其他点的容量,而是考虑每条边对总容量的贡献
  2. 排序策略:将所有边按容量从大到小排序,这样保证优先处理容量大的边
  3. 合并过程:使用并查集维护连通分量,每个分量记录:
    • 分量大小(节点数)
    • 当前分量作为中心时的总容量
  4. 合并决策:当合并两个连通分量时,计算两种可能的中心选择方案,选择总容量更大的方案

算法详解

核心思想

假设我们按容量降序处理边,当处理边 (u,v)(u, v)(u,v) 时:

  • uuu 所在的连通分量为 AAA,大小为 sizeAsize_AsizeA,当前总容量为 sumAsum_AsumA
  • vvv 所在的连通分量为 BBB,大小为 sizeBsize_BsizeB,当前总容量为 sumBsum_BsumB

有两种合并方案:

  1. AAA 为中心:总容量 = sumA+sizeB×capsum_A + size_B \times capsumA+sizeB×cap
  2. BBB 为中心:总容量 = sumB+sizeA×capsum_B + size_A \times capsumB+sizeA×cap

我们选择总容量更大的方案进行合并。

为什么这样正确?

  • 贪心正确性:由于按容量降序处理,当前边是连接两个分量的最小容量边,因此两个分量中任意两点间的交通容量不会超过当前边容量
  • 最优子结构:每次合并都保证了当前连通分量的总容量是局部最优的
  • 最终结果:当所有边处理完成后,整个树形成一个连通分量,其记录的总容量就是全局最优解

时间复杂度

  • 排序:O(Nlog⁡N)O(N \log N)O(NlogN)
  • 并查集操作:O(Nα(N))O(N \alpha(N))O(Nα(N)),其中 α\alphaα 是反阿克曼函数
  • 总体复杂度:O(Nlog⁡N)O(N \log N)O(NlogN),满足题目约束

代码实现

// Conquer a New Region
// UVa ID: 1664
// Verdict: Accepted
// Submission Date: 2025-11-26
// UVa Run Time: 0.200s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 200010;

// 并查集数据结构
struct DSU {
    int parent[MAXN];
    long long size[MAXN];  // 连通分量的大小
    long long sum[MAXN];   // 连通分量的总容量
    
    void init(int n) {
        for (int i = 1; i <= n; i++) {
            parent[i] = i;
            size[i] = 1;
            sum[i] = 0;
        }
    }
    
    int find(int x) {
        if (parent[x] != x)
            parent[x] = find(parent[x]);
        return parent[x];
    }
};

struct Edge {
    int u, v, cap;
    bool operator<(const Edge& other) const {
        return cap > other.cap;  // 按容量降序排序
    }
};

Edge edges[MAXN];
DSU dsu;

int main() {
    int n;
    while (scanf("%d", &n) != EOF) {
        // 读取所有边
        for (int i = 0; i < n - 1; i++) 
            scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].cap);
        
        // 初始化并查集
        dsu.init(n);
        
        // 按容量降序排序边
        sort(edges, edges + n - 1);
        
        // 处理每条边
        for (int i = 0; i < n - 1; i++) {
            int u = edges[i].u, v = edges[i].v, cap = edges[i].cap;
            int rootU = dsu.find(u);
            int rootV = dsu.find(v);
            
            // 计算两个连通分量作为新中心的总容量
            long long sumU = dsu.sum[rootU] + dsu.size[rootV] * cap;
            long long sumV = dsu.sum[rootV] + dsu.size[rootU] * cap;
            
            // 合并连通分量,选择总容量更大的作为新值
            if (sumU > sumV) {
                dsu.parent[rootV] = rootU;
                dsu.size[rootU] += dsu.size[rootV];
                dsu.sum[rootU] = sumU;
            } else {
                dsu.parent[rootU] = rootV;
                dsu.size[rootV] += dsu.size[rootU];
                dsu.sum[rootV] = sumV;
            }
        }
        
        // 最终答案在唯一的连通分量的sum中
        int root = dsu.find(1);
        printf("%lld\n", dsu.sum[root]);
    }
    return 0;
}

总结

本题展示了如何将看似复杂的最优化问题转化为经典的并查集应用。关键在于:

  1. 利用树结构的特性简化问题
  2. 通过排序和贪心策略确保最优解
  3. 在并查集中维护额外的信息来支持决策

这种"逆向思维"和"按权重处理"的技巧在很多图论问题中都非常有用,值得深入理解和掌握。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值