UVa 10989 Bomb Divide and Conquer

题目描述

敌人有 nnn 个城市,通过 mmm 条道路连接。我们拥有可以摧毁道路的轰炸机。"轰炸、分治"策略指出,如果我们将敌人的城市彼此分离,那么两个(或更多)部分将更容易控制。轰炸一条道路有相应的成本(燃料、风险因素等)。确保存在至少一对城市之间没有路径所需的最小总轰炸成本是多少?

输入格式

  • 第一行给出测试用例的数量 NNN
  • 每个测试用例包含:
    • 两个整数 nnn (2≤n≤1502 \leq n \leq 1502≤n≤150) 和 mmm (0≤m≤n(n−1)/20 \leq m \leq n(n-1)/20≤m≤n(n−1)/2)
    • mmm 行,每行包含三个整数:两个不同的城市编号(111 到 nnn)和摧毁该道路的成本(111 到 100010001000)

输出格式

  • 对于每个测试用例,输出一行:Case #x: 后跟断开某对城市连接的总成本

题目分析

问题本质

题目要求通过摧毁一些道路,使得图变得不连通,即至少存在一对城市之间没有路径。这等价于将原图分成至少两个连通分量。

我们需要找到摧毁道路的最小总成本,使得图不再连通。

关键洞察

  1. 图连通性 :如果原图已经是不连通的,那么不需要摧毁任何道路,答案为 000

  2. 最小割问题 :对于连通图,我们需要找到最小割 ------将顶点分成两个集合 SSS 和 TTT,使得连接 SSS 和 TTT 的所有边的权重之和最小

  3. 最大流最小割定理 :在流网络中,从源点 sss 到汇点 ttt 的最大流值等于分离 sss 和 ttt 的最小割的容量

算法选择

由于 n≤150n \leq 150n≤150,我们可以使用以下方法:

  • 固定一个源点 sss
  • 枚举所有其他顶点作为汇点 ttt
  • 对于每个 (s,t)(s, t)(s,t) 对,计算最大流(即最小割)
  • 取所有 (s,t)(s, t)(s,t) 对的最小割的最小值作为答案

我们选择 Dinic\texttt{Dinic}Dinic 算法 来计算最大流,因为它在实践中效率较高。

复杂度分析

  • Dinic\texttt{Dinic}Dinic 算法复杂度:O(V2E)O(V^2E)O(V2E)
  • 我们需要计算 n−1n-1n−1 次最大流
  • 总复杂度:O(n⋅V2E)O(n \cdot V^2E)O(n⋅V2E),在 n≤150n \leq 150n≤150 时可行

解题思路详解

步骤 1:问题建模

将问题转化为图论模型:

  • 顶点:城市(编号 000 到 n−1n-1n−1)
  • 边:道路,边权 = 摧毁成本
  • 目标:找到全局最小割

步骤 2:算法原理

根据全局最小割定理,无向图的全局最小割可以通过以下方式求得:

  1. 固定一个源点 sss(通常选择顶点 000)
  2. 对于每个其他顶点 ttt,计算 sss-ttt 最小割
  3. 全局最小割 = min⁡t≠smincut(s,t)\min\limits_{t \neq s} \text{mincut}(s, t)t=sminmincut(s,t)

步骤 3:Dinic\texttt{Dinic}Dinic 算法实现

Dinic\texttt{Dinic}Dinic 算法包含三个主要部分:

  1. 分层图构建 (BFS\texttt{BFS}BFS):

    • 从源点开始,为每个顶点分配层级
    • 只允许从低层级向高层级发送流量
  2. 阻塞流计算 (DFS\texttt{DFS}DFS):

    • 在当前分层图中寻找增广路径
    • 使用当前弧优化避免重复检查边
  3. 迭代过程

    • 重复构建分层图和计算阻塞流,直到无法找到增广路径

步骤 4:特殊情况处理

  • 如果原图不连通,最小割为 000
  • 对于单边情况,最小割可能是该边的权重
  • 对于复杂网络,需要计算多个 (s,t)(s, t)(s,t) 对的最小割

代码实现

cpp 复制代码
// Bomb Divide and Conquer
// UVa ID: 10989
// Verdict: Accepted
// Submission Date: 2025-11-02
// UVa Run Time: 0.160s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>

using namespace std;

/**
 * Dinic 最大流算法类
 * 用于计算无向图的最小割
 */
struct DinicMaxFlow {
    // 边结构体
    struct FlowEdge {
        int targetNode;          // 目标节点
        int reverseEdgeIndex;    // 反向边在邻接表中的索引
        int capacity;            // 边容量
        int currentFlow;         // 当前流量
    };

    int totalNodes;                      // 总节点数
    vector<vector<FlowEdge>> graph;      // 图的邻接表表示
    vector<int> nodeLevel;               // 节点的层级(用于分层图)
    vector<int> currentEdgePointer;      // 当前弧指针(用于优化)

    // 构造函数
    DinicMaxFlow(int nodeCount) : totalNodes(nodeCount), 
                                 graph(nodeCount), 
                                 nodeLevel(nodeCount), 
                                 currentEdgePointer(nodeCount) {}

    /**
     * 添加无向边
     * @param from 起始节点
     * @param to 目标节点  
     * @param cap 边的容量
     */
    void addUndirectedEdge(int from, int to, int cap) {
        // 添加正向边
        graph[from].push_back({to, (int)graph[to].size(), cap, 0});
        // 添加反向边(无向图,容量相同)
        graph[to].push_back({from, (int)graph[from].size() - 1, cap, 0});
    }

    /**
     * 使用 BFS 构建分层图
     * @param source 源点
     * @param sink 汇点
     * @return 如果汇点可达返回 true,否则 false
     */
    bool buildLevelGraph(int source, int sink) {
        fill(nodeLevel.begin(), nodeLevel.end(), -1);
        nodeLevel[source] = 0;
        queue<int> bfsQueue;
        bfsQueue.push(source);
        
        while (!bfsQueue.empty()) {
            int currentNode = bfsQueue.front();
            bfsQueue.pop();
            
            for (const FlowEdge& edge : graph[currentNode]) {
                if (nodeLevel[edge.targetNode] == -1 && edge.currentFlow < edge.capacity) {
                    nodeLevel[edge.targetNode] = nodeLevel[currentNode] + 1;
                    bfsQueue.push(edge.targetNode);
                }
            }
        }
        return nodeLevel[sink] != -1;
    }

    /**
     * 使用 DFS 在分层图中寻找阻塞流
     * @param currentNode 当前节点
     * @param sink 汇点
     * @param minCapacity 路径上的最小容量
     * @return 实际推送的流量
     */
    int findBlockingFlow(int currentNode, int sink, int minCapacity) {
        if (currentNode == sink) return minCapacity;
        
        for (int& edgeIndex = currentEdgePointer[currentNode]; 
             edgeIndex < (int)graph[currentNode].size(); 
             edgeIndex++) {
            
            FlowEdge& edge = graph[currentNode][edgeIndex];
            
            if (nodeLevel[edge.targetNode] == nodeLevel[currentNode] + 1 && 
                edge.currentFlow < edge.capacity) {
                
                int pushedFlow = findBlockingFlow(edge.targetNode, sink, 
                                                 min(minCapacity, edge.capacity - edge.currentFlow));
                
                if (pushedFlow > 0) {
                    edge.currentFlow += pushedFlow;
                    graph[edge.targetNode][edge.reverseEdgeIndex].currentFlow -= pushedFlow;
                    return pushedFlow;
                }
            }
        }
        return 0;
    }

    /**
     * 计算从 source 到 sink 的最大流
     * @param source 源点
     * @param sink 汇点
     * @return 最大流值(即最小割值)
     */
    int computeMaxFlow(int source, int sink) {
        int totalFlow = 0;
        
        // 反复构建分层图并寻找阻塞流
        while (buildLevelGraph(source, sink)) {
            fill(currentEdgePointer.begin(), currentEdgePointer.end(), 0);
            
            while (int pushedFlow = findBlockingFlow(source, sink, INT_MAX)) {
                totalFlow += pushedFlow;
            }
        }
        
        // 重置所有边的流量,为下一次计算做准备
        resetFlow();
        return totalFlow;
    }

private:
    /**
     * 重置图中所有边的流量为 0
     */
    void resetFlow() {
        for (int i = 0; i < totalNodes; i++) {
            for (FlowEdge& edge : graph[i]) {
                edge.currentFlow = 0;
            }
        }
    }
};

int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);

    int T;
    cin >> T;
    
    for (int cs= 1; cs <= T; cs++) {
        int cityCount, roadCount;
        cin >> cityCount >> roadCount;
        
        // 初始化 Dinic 算法实例
        DinicMaxFlow flowSolver(cityCount);
        
        // 读取道路信息并构建图
        for (int i = 0; i < roadCount; i++) {
            int cityA, cityB, destroyCost;
            cin >> cityA >> cityB >> destroyCost;
            cityA--; cityB--;  // 转换为 0-based 索引
            flowSolver.addUndirectedEdge(cityA, cityB, destroyCost);
        }

        // 计算全局最小割
        int minCutValue = INT_MAX;
        int sourceNode = 0;  // 固定源点为第一个城市
        
        // 枚举所有其他城市作为汇点
        for (int sinkNode = 1; sinkNode < cityCount; sinkNode++) {
            minCutValue = min(minCutValue, flowSolver.computeMaxFlow(sourceNode, sinkNode));
        }
        
        // 处理图原本不连通的情况
        if (minCutValue == INT_MAX) minCutValue = 0;
        
        cout << "Case #" << caseNumber << ": " << minCutValue << '\n';
    }

    return 0;
}

总结

本题的关键在于将实际问题转化为图论中的最小割问题 ,并利用最大流最小割定理 来求解。通过固定源点、枚举汇点的方法,结合高效的 Dinic\texttt{Dinic}Dinic 最大流算法,我们能够在合理的时间内解决问题。

算法的时间复杂度为 O(n⋅V2E)O(n \cdot V^2E)O(n⋅V2E),空间复杂度为 O(V+E)O(V + E)O(V+E),对于题目给定的数据范围 (n≤150n \leq 150n≤150) 是完全可行的。

相关推荐
gfdhy16 小时前
【c++】哈希算法深度解析:实现、核心作用与工业级应用
c语言·开发语言·c++·算法·密码学·哈希算法·哈希
百***060116 小时前
SpringMVC 请求参数接收
前端·javascript·算法
一个不知名程序员www17 小时前
算法学习入门---vector(C++)
c++·算法
云飞云共享云桌面17 小时前
无需配置传统电脑——智能装备工厂10个SolidWorks共享一台工作站
运维·服务器·前端·网络·算法·电脑
福尔摩斯张18 小时前
《C 语言指针从入门到精通:全面笔记 + 实战习题深度解析》(超详细)
linux·运维·服务器·c语言·开发语言·c++·算法
橘颂TA18 小时前
【剑斩OFFER】算法的暴力美学——两整数之和
算法·leetcode·职场和发展
xxxxxxllllllshi18 小时前
【LeetCode Hot100----14-贪心算法(01-05),包含多种方法,详细思路与代码,让你一篇文章看懂所有!】
java·数据结构·算法·leetcode·贪心算法
前端小L19 小时前
图论专题(二十二):并查集的“逻辑审判”——判断「等式方程的可满足性」
算法·矩阵·深度优先·图论·宽度优先
铁手飞鹰19 小时前
二叉树(C语言,手撕)
c语言·数据结构·算法·二叉树·深度优先·广度优先
专业抄代码选手20 小时前
【Leetcode】1930. 长度为 3 的不同回文子序列
javascript·算法·面试