UVa 11603 It‘s all about the Bandwidth

题目描述

我的公寓里有 nnn 台计算机,我朋友的公寓里也有 nnn 台计算机。在每个公寓内,一些计算机对之间通过 AcidNet\texttt{AcidNet}AcidNet 电缆连接(忽略路由器)。每条连接都有特定的带宽(字节/秒)。我的朋友总是吹嘘他的计算机网络速度,并向我展示他的 n×nn \times nn×n 带宽表格,其中列出了每对计算机之间的带宽。

我的网络较慢,我想重建它。我需要知道如何连接我的计算机,才能得到相同的 n×nn \times nn×n 带宽表格。由于我不想购买太多 AcidNet\texttt{AcidNet}AcidNet 电缆,需要找到连接数最少的解决方案。我可以使用任何整数带宽的 AcidNet\texttt{AcidNet}AcidNet 电缆,它们在商店中价格相同。

问题本质:给定一个 n×nn \times nn×n 的对称表格 TTT,其中 T[u][v]T[u][v]T[u][v] 表示计算机 uuuvvv 之间的最大流(带宽),找到一个边数最少的无向图,使得该图中任意两点 u,vu, vu,v 之间的最大流等于 T[u][v]T[u][v]T[u][v]

输入格式

  • 第一行:测试用例数 NNN
  • 每个测试用例:
    • 一行包含 nnn (0<n≤2000 < n \leq 2000<n200)
    • nnn 行,每行 nnn 个整数,表示表格 TTT
    • T[u][u]=0T[u][u] = 0T[u][u]=0
    • T[u][v]=T[v][u]>0T[u][v] = T[v][u] > 0T[u][v]=T[v][u]>0
    • T[i][j]≤10000T[i][j] \leq 10000T[i][j]10000

输出格式

对于每个测试用例:

  • 如果无解:输出 Impossible
  • 如果有解:输出边数 mmm,然后 mmm 行,每行三个整数 u v wu\ v\ wu v w 表示连接 uuuvvv 的电缆带宽为 www

题目分析

问题本质理解

这是一个图论逆问题:给定所有点对之间的最大流值(即最小割容量),要求重构出边数最少的原图。

关键观察:

  1. 在无向图中,两点间的最大流等于它们之间的最小割容量
  2. 在树结构中,两点 u,vu, vu,v 之间的最大流等于连接路径上的最小边权
  3. 为了最小化边数,我们希望找到一棵树(n−1n-1n1 条边)

理论基础

重要定理:如果对称表格 TTT 满足以下条件,则存在一棵树使得任意两点 u,vu, vu,v 在树路径上的最小边权等于 T[u][v]T[u][v]T[u][v]

  1. T[u][u]=0T[u][u] = 0T[u][u]=0(对角线为 0)
  2. T[u][v]=T[v][u]T[u][v] = T[v][u]T[u][v]=T[v][u](对称性)
  3. 对于所有 i,j,ki, j, ki,j,kk≠i,k≠jk \neq i, k \neq jk=i,k=j),满足三角不等式:
    T[i][j]≥min⁡(T[i][k],T[k][j])T[i][j] \geq \min(T[i][k], T[k][j])T[i][j]min(T[i][k],T[k][j])

算法思路

  1. 可行性检查:验证表格 TTT 是否满足三角不等式
  2. 构建最大生成树:如果满足条件,使用 Kruskal\texttt{Kruskal}Kruskal 算法构建最大生成树
  3. 输出结果:直接输出最大生成树的所有边

为什么使用最大生成树?

  • 在最小生成树中,路径上的最小边权是瓶颈
  • 为了满足 T[u][v]T[u][v]T[u][v] 的要求,需要路径上的最小边权尽可能大
  • 最大生成树能保证对于任意两点 u,vu, vu,v,它们之间路径的最小边权等于 T[u][v]T[u][v]T[u][v]

解题步骤详解

步骤 1:输入与验证

读取输入数据并验证基本性质:

  • 对角线必须为 0
  • 矩阵必须对称
  • 所有值非负

步骤 2:三角不等式检查

对于所有 i<ji < ji<jk≠i,k≠jk \neq i, k \neq jk=i,k=j,检查:
T[i][j]≥min⁡(T[i][k],T[k][j])T[i][j] \geq \min(T[i][k], T[k][j])T[i][j]min(T[i][k],T[k][j])

如果任何一对违反此条件,则输出 Impossible

步骤 3:构建最大生成树

  1. 将所有可能的边 (i,j,T[i][j])(i, j, T[i][j])(i,j,T[i][j]) 加入边集
  2. 按边权降序排序(最大生成树)
  3. 使用 Kruskal\texttt{Kruskal}Kruskal 算法构建生成树
  4. 使用并查集管理连通分量

步骤 4:输出结果

输出树的 n−1n-1n1 条边,确保每条边只输出一次(u<vu < vu<v)。

复杂度分析

  • 时间复杂度O(n3)O(n^3)O(n3)
    • 三角不等式检查:O(n3)O(n^3)O(n3)
    • 排序边:O(n2log⁡n)O(n^2 \log n)O(n2logn)
    • Kruskal\texttt{Kruskal}Kruskal 算法:O(n2α(n))O(n^2 \alpha(n))O(n2α(n))
  • 空间复杂度O(n2)O(n^2)O(n2)

代码实现

// It's all about the Bandwidth
// UVa ID: 11603
// Verdict: Accepted
// Submission Date: 2025-11-17
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

struct Edge {
    int u, v, w;
    bool operator<(const Edge& other) const {
        return w > other.w; // 最大生成树
    }
};

vector<int> parent;

int findSet(int x) {
    if (parent[x] != x)
        parent[x] = findSet(parent[x]);
    return parent[x];
}

bool unionSet(int x, int y) {
    int rx = findSet(x), ry = findSet(y);
    if (rx == ry) return false;
    parent[rx] = ry;
    return true;
}

void solve() {
    int n;
    cin >> n;
    vector<vector<int>> t(n, vector<int>(n));
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cin >> t[i][j];

    // 检查三角不等式,忽略 k=i 或 k=j 的情况
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            for (int k = 0; k < n; k++) {
                if (k != i && k != j && t[i][j] < min(t[i][k], t[k][j])) {
                    cout << "Impossible\n";
                    return;
                }
            }
        }
    }

    // 构建边列表
    vector<Edge> edges;
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            edges.push_back({i, j, t[i][j]});

    sort(edges.begin(), edges.end());

    // 最大生成树
    parent.resize(n);
    for (int i = 0; i < n; i++) parent[i] = i;

    vector<vector<pair<int, int>>> tree(n);
    int mstEdges = 0;
    for (const auto& e : edges) {
        if (unionSet(e.u, e.v)) {
            tree[e.u].push_back({e.v, e.w});
            tree[e.v].push_back({e.u, e.w});
            mstEdges++;
            if (mstEdges == n - 1) break;
        }
    }

    // 输出结果
    cout << n - 1 << "\n";
    for (int u = 0; u < n; u++)
        for (const auto& nb : tree[u])
            if (u < nb.first)
                cout << u << " " << nb.first << " " << nb.second << "\n";
}

int main() {
    int N;
    cin >> N;
    for (int caseNum = 1; caseNum <= N; caseNum++) {
        cout << "Case #" << caseNum << ": ";
        solve();
    }
    return 0;
}

总结

本题的关键在于理解最大流表格与树结构之间的关系。通过三角不等式的验证和最大生成树的构建,我们能够高效地解决这个图论逆问题。算法的时间复杂度为 O(n3)O(n^3)O(n3),在 n≤200n \leq 200n200 的约束下完全可行。

这种将复杂问题转化为经典图论算法(如最大生成树)的思路,在竞赛中非常实用,值得学习和掌握。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值