当前位置: 首页 > news >正文

UVa 11183 Teen Girl Squad

题目分析

问题描述

我们有一个包含 nnn 个女孩的社交网络,编号从 000n−1n-1n1,其中 000 号女孩是消息的起点。女孩之间可以通过有向的电话呼叫传播消息,每个呼叫 (u,v,w)(u, v, w)(u,v,w) 表示 uuu 可以打电话给 vvv,费用为 www 分。

目标:从 000 号女孩出发,通过一系列电话呼叫让所有女孩都听到消息,并且总费用最小。

约束

  • 如果无法让所有女孩都收到消息,输出 Possums!\texttt{Possums!}Possums!
  • 输入规模:n≤1000n \leq 1000n1000m≤40000m \leq 40000m40000

问题本质

这是一个典型的有向图最小生成树问题,更准确地说,是最小树形图Minimum Spanning Arborescence\texttt{Minimum Spanning Arborescence}Minimum Spanning Arborescence)问题:

  • 有向性:电话呼叫是单向的
  • 根节点固定:必须从 000 号女孩开始
  • 连通性要求:必须到达所有节点

算法选择

对于有向图的最小生成树问题,常用的算法是朱-刘算法Chu–Liu/Edmonds’ algorithm\texttt{Chu–Liu/Edmonds' algorithm}ChuLiu/Edmonds’ algorithm),该算法专门解决有向图中从固定根节点出发的最小树形图问题。

朱-刘算法详解

算法步骤

  1. 寻找最小入边:对于每个非根节点,选择权值最小的入边
  2. 检测环:检查这些边是否形成环
    • 如果无环,则已找到最小树形图,算法结束
    • 如果有环,继续步骤3
  3. 缩环:将环收缩为一个超级节点,更新相关边的权值
  4. 重复过程:在收缩后的图上重复上述步骤

算法复杂度

  • 时间复杂度:O(n×m)O(n \times m)O(n×m)
  • 空间复杂度:O(n+m)O(n + m)O(n+m)

关键点说明

  • 入边选择:每个非根节点必须至少有一条入边,否则无解
  • 权值更新:缩环时,指向环内节点的边权值需要减去环内该节点入边的权值
  • 解的存在性:如果某个非根节点没有入边,则无法形成树形图

代码实现

// Teen Girl Squad
// UVa ID: 11183
// Verdict: Accepted
// Submission Date: 2025-10-24
// UVa Run Time: 0.030s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>
using namespace std;const int INF = 0x3f3f3f3f;
const int MAX_NODE = 1005;struct PhoneCall {int caller;  // 呼叫者int receiver; // 接收者  int cost;    // 呼叫费用
};int parentNode[MAX_NODE];    // 记录每个节点的前驱节点
int nodeId[MAX_NODE];        // 节点在新图中的编号
int visited[MAX_NODE];       // 访问标记数组
int minIncomingCost[MAX_NODE]; // 每个节点的最小入边费用/*** 朱-刘算法求解有向图最小树形图* @param rootNode 根节点编号* @param nodeCount 节点总数* @param callRecords 电话呼叫记录列表* @return 最小总费用,无解返回-1*/
int findMinCallCost(int rootNode, int nodeCount, vector<PhoneCall>& callRecords) {int totalCost = 0;int callCount = callRecords.size();while (true) {// 步骤1:为每个节点寻找最小入边fill(minIncomingCost, minIncomingCost + nodeCount, INF);for (int i = 0; i < callCount; i++) {int caller = callRecords[i].caller;int receiver = callRecords[i].receiver;if (caller != receiver && callRecords[i].cost < minIncomingCost[receiver]) {parentNode[receiver] = caller;minIncomingCost[receiver] = callRecords[i].cost;}}// 检查非根节点是否都有入边for (int i = 0; i < nodeCount; i++) {if (i != rootNode && minIncomingCost[i] == INF) {return -1; // 存在节点不可达,无解}}// 步骤2:检测环int newIdCount = 0;fill(nodeId, nodeId + nodeCount, -1);fill(visited, visited + nodeCount, -1);minIncomingCost[rootNode] = 0; // 根节点无入边for (int i = 0; i < nodeCount; i++) {totalCost += minIncomingCost[i];int currentNode = i;// 沿着前驱链寻找环while (visited[currentNode] != i && nodeId[currentNode] == -1 && currentNode != rootNode) {visited[currentNode] = i;currentNode = parentNode[currentNode];}// 找到新环,进行标记if (currentNode != rootNode && nodeId[currentNode] == -1) {for (int node = parentNode[currentNode]; node != currentNode; node = parentNode[node]) {nodeId[node] = newIdCount;}nodeId[currentNode] = newIdCount++;}}if (newIdCount == 0) break; // 无环,算法结束// 为不在环中的节点分配新编号for (int i = 0; i < nodeCount; i++) {if (nodeId[i] == -1) {nodeId[i] = newIdCount++;}}// 步骤3:缩环,更新边权for (int i = 0; i < callCount; i++) {int originalCaller = callRecords[i].caller;int originalReceiver = callRecords[i].receiver;callRecords[i].caller = nodeId[originalCaller];callRecords[i].receiver = nodeId[originalReceiver];if (callRecords[i].caller != callRecords[i].receiver) {callRecords[i].cost -= minIncomingCost[originalReceiver];}}nodeCount = newIdCount;rootNode = nodeId[rootNode];}return totalCost;
}int main() {ios::sync_with_stdio(false);cin.tie(0);int testCaseCount;cin >> testCaseCount;for (int caseIndex = 1; caseIndex <= testCaseCount; caseIndex++) {int girlCount, callCount;cin >> girlCount >> callCount;vector<PhoneCall> callRecords(callCount);for (int i = 0; i < callCount; i++) {cin >> callRecords[i].caller >> callRecords[i].receiver >> callRecords[i].cost;}int minCost = findMinCallCost(0, girlCount, callRecords);cout << "Case #" << caseIndex << ": ";if (minCost == -1) {cout << "Possums!\n";} else {cout << minCost << "\n";}}return 0;
}

代码说明

数据结构

  • PhoneCall:存储电话呼叫信息
  • minIncomingCost:记录每个节点的最小入边费用
  • parentNode:记录最小入边的来源节点
  • nodeId:节点在缩环后的新编号
  • visited:访问标记,用于环检测

关键函数

  • findMinCallCost:实现朱-刘算法核心逻辑
    • 循环执行找最小入边、检测环、缩环的过程
    • 返回最小总费用或无解标记

复杂度分析

  • 最坏情况下需要 O(n)O(n)O(n) 次迭代
  • 每次迭代处理 mmm 条边
  • 总复杂度 O(n×m)O(n \times m)O(n×m),在题目限制内可行

总结

本题通过朱-刘算法优雅地解决了有向图最小生成树问题。算法的核心在于不断检测并收缩环,同时更新边权,直到得到无环的树形结构。该算法在有向图连通性检测和最小代价传播路径选择方面具有重要应用价值。

http://www.dtcms.com/a/525858.html

相关文章:

  • 医疗教育的网站建设山西公司网站建设
  • CompositionLocal 用法
  • 怎样设计一个网站平台免费seo课程
  • EFM8开发系列
  • 哈尔滨网站优化咨询wordpress哪个模版好用
  • 网站策划ppt自己申请网站空间
  • 阿里云国际站GPU:阿里云GPU怎么释放实例?
  • Linux网络UDP(10)
  • 网站培训制度wap网站开发流程
  • 稳定网站服务器租用厦门网站建设公司
  • Linux实现每3天23点定时重启服务器
  • wordpress公司网站郑州网站seo服务
  • 【数据合成】Socratic-Zero 自动合成数据 指导课程学习
  • 2024包河初中组
  • “我店 + 微团“ 如何做到 “高频带低频“?拆解用户终身价值挖掘路径
  • 企业网站内容模块站酷官网入口
  • 沈阳做一个网站需要多少钱做c语言题目的网站
  • 涪城移动网站建设分销平台搭建
  • 想做个网站 怎么做建设企业网站电话
  • 网站优化推广公司织梦电影网站源码
  • 企业签合同,已进入“快签时代”
  • 【小白笔记】两数之和
  • 个人域名网站可以做企业站吗无广告自助建站
  • 网站下载的wordpress模板如何添加05网英语书
  • 从零使用vue脚手架开发一个简易的计算器
  • 兰州做网站价格网站开发成本计算
  • 1024水个贴
  • 怎么将自己做的网站放到网上seo外链推广平台
  • 产品策划书模板南昌seo排名
  • 韩国做美食的视频网站购物网站首页界面设计