题目描述

小 C 最近学了很多最小生成树的算法,Prim 算法、Kruskal 算法、消圈算法等等。正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是 EM​,严格次小生成树选择的边集是 ES​,那么需要满足:(value(e) 表示边 e 的权值) ∑e∈EM​​value(e)<∑e∈ES​​value(e)。

这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

输入格式

第一行包含两个整数 N 和 M,表示无向图的点数与边数。

接下来 M 行,每行 3 个数 x,y,z 表示,点 x 和点 y 之间有一条边,边的权值为 z。

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。

输入输出样例

输入 #1复制

5 6
1 2 1 
1 3 2 
2 4 3 
3 5 4 
3 4 3 
4 5 6 

输出 #1复制

11

说明/提示

数据中无向图不保证无自环

对于 50% 的数据, N≤2000,M≤3000。

对于 80% 的数据, N≤5×104,M≤105。

对于 100% 的数据, N≤105,M≤3×105,边权 ∈[0,109],数据保证必定存在严格次小生成树。

代码实现:

#include<cstdio>
#include<algorithm>
#define maxVal(a,b) ((a)>(b)?(a):(b))  // 取两个值中的最大值

unsigned int nodeCount, edgeCount;  // 节点数量和边数量

// 存储图中边的信息
struct Edge
{
unsigned int from;    // 边的起点
unsigned int to;      // 边的终点
unsigned int weight;  // 边的权重
bool isInMST;         // 标记该边是否在最小生成树中
// 重载小于运算符,用于按权重排序
bool operator < (const Edge &other) const
{
return weight < other.weight;
}
} edges[300005];  // 存储所有边的数组

unsigned int parent[100005];  // 并查集数组,存储每个节点的父节点

// 并查集查找根节点,带路径压缩
unsigned int findRoot(unsigned int node)
{
if(parent[node] == node)
{
return parent[node];
}
parent[node] = findRoot(parent[node]);
return parent[node];
}

unsigned int mstEdgeCount;  // 最小生成树中的边数
unsigned long long int mstTotalWeight;  // 最小生成树的总权重

// 用于存储生成树的邻接表节点
struct TreeNode
{
unsigned int target;   // 目标节点
unsigned int nextNode; // 下一个邻接节点
unsigned int weight;   // 边的权重
} treeNodes[200005];  // 邻接表节点数组

unsigned int adjListCount;  // 邻接表计数器
unsigned int adjListHead[100005];  // 邻接表头指针数组

// 向邻接表中添加边
void addEdgeToAdjList(unsigned int from, unsigned int to, unsigned int weight)
{
adjListCount++;
treeNodes[adjListCount].target = to;
treeNodes[adjListCount].nextNode = adjListHead[from];
adjListHead[from] = adjListCount;
treeNodes[adjListCount].weight = weight;
}

// 倍增数组
unsigned int depth[100005];  // 节点在树上的深度
unsigned int maxEdgeWeight[100005][20];  // 路径上的最大边权
unsigned int secondMaxEdgeWeight[100005][20];  // 路径上的次大边权
unsigned int ancestor[100005][20];  // 祖先节点

// DFS初始化:计算节点深度、初始最大边权和祖先
void dfs(unsigned int currentNode)
{
for(unsigned int i = adjListHead[currentNode]; i != 0; i = treeNodes[i].nextNode)
{
if(treeNodes[i].target != ancestor[currentNode][0])
{
depth[treeNodes[i].target] = depth[currentNode] + 1;
maxEdgeWeight[treeNodes[i].target][0] = treeNodes[i].weight;
ancestor[treeNodes[i].target][0] = currentNode;
dfs(treeNodes[i].target);
}
}
}

unsigned long long int result = 999999999999999ull;  // 结果:次小生成树的权重

// 树上倍增法求最近公共祖先(LCA)
unsigned int findLCA(unsigned int u, unsigned int v)
{
// 确保u的深度大于等于v的深度
if(depth[u] < depth[v])
{
// 交换u和v
unsigned int temp;
temp = u;
u = v;
v = temp;
}

unsigned int i;
// 找到u深度对应的最大二进制位数
for(i = 0; i <= 18; i++)
{
if((1 << i) > depth[u])
{
break;
}
}
i--;

// 将u上移到与v相同的深度
for(int j = i; j >= 0; j--)
{
if(depth[u] >= depth[v] + (1 << j))
{
u = ancestor[u][j];
}
}

// 如果此时u和v相同,说明已经找到LCA
if(u == v)
{
return u;
}

// 从最大的二进制位开始,向上移动u和v直到它们的祖先相同
for(int j = 18; j >= 0; j--)
{
if(ancestor[u][j] != ancestor[v][j])
{
u = ancestor[u][j];
v = ancestor[v][j];
}
}

// 返回最终的LCA
return ancestor[u][0];
}

// 获取新增边构成的环中的最大边权(或次大边权)
unsigned int findMaxInCycle(unsigned int u, unsigned int v, unsigned int weight)
{
unsigned int lca = findLCA(u, v);
unsigned int i, maxLeft = 0, maxRight = 0;

// 处理u到LCA的路径
for(i = 0; i <= 18; i++)
{
if((1 << i) > depth[u])
{
break;
}
}
i--;

for(int j = i; j >= 0; j--)
{
if(depth[u] >= depth[lca] + (1 << j))
{
if(maxEdgeWeight[u][j] != weight)
{
maxLeft = maxVal(maxLeft, maxEdgeWeight[u][j]);
}
else
{
maxLeft = maxVal(maxLeft, secondMaxEdgeWeight[u][j]);
}
u = ancestor[u][j];
}
}

// 处理v到LCA的路径
for(i = 0; i <= 18; i++)
{
if((1 << i) > depth[v])
{
break;
}
}
i--;

for(int j = i; j >= 0; j--)
{
if(depth[v] >= depth[lca] + (1 << j))
{
if(maxEdgeWeight[v][j] != weight)
{
maxRight = maxVal(maxRight, maxEdgeWeight[v][j]);
}
else
{
maxRight = maxVal(maxRight, secondMaxEdgeWeight[v][j]);
}
v = ancestor[v][j];
}
}

return maxVal(maxLeft, maxRight);
}

int main()
{
scanf("%u%u", &nodeCount, &edgeCount);

// 初始化并查集
for(unsigned int i = 1; i <= nodeCount; i++)
{
parent[i] = i;
}

// 读入所有边
for(unsigned int i = 1; i <= edgeCount; i++)
{
scanf("%u%u%u", &edges[i].from, &edges[i].to, &edges[i].weight);
edges[i].isInMST = 0;
}

// 按权重排序边
std::sort(edges + 1, edges + edgeCount + 1);

// Kruskal算法构建最小生成树
for(unsigned short int i = 1; i <= edgeCount; i++)
{
unsigned int rootU = findRoot(edges[i].from);
unsigned int rootV = findRoot(edges[i].to);

if(rootU != rootV)
{
mstEdgeCount++;
mstTotalWeight += edges[i].weight;
edges[i].isInMST = 1;
parent[rootV] = rootU;
addEdgeToAdjList(edges[i].from, edges[i].to, edges[i].weight);
addEdgeToAdjList(edges[i].to, edges[i].from, edges[i].weight);

// 最小生成树已完成(n-1条边)
if(mstEdgeCount == nodeCount - 1)
{
break;
}
}
}

// 初始化深度、最大边权和祖先
dfs(1);

// 预处理倍增数组
for(unsigned short int i = 1; i <= 18; i++)
{
for(unsigned int j = 1; j <= nodeCount; j++)
{
ancestor[j][i] = ancestor[ancestor[j][i-1]][i-1];
maxEdgeWeight[j][i] = maxVal(maxEdgeWeight[j][i-1], maxEdgeWeight[ancestor[j][i-1]][i-1]);
secondMaxEdgeWeight[j][i] = maxVal(secondMaxEdgeWeight[j][i-1], secondMaxEdgeWeight[ancestor[j][i-1]][i-1]);

// 更新次大边权
if(maxEdgeWeight[j][i-1] < maxEdgeWeight[ancestor[j][i-1]][i-1] && 
secondMaxEdgeWeight[j][i] < maxEdgeWeight[j][i-1])
{
secondMaxEdgeWeight[j][i] = maxEdgeWeight[j][i-1];
}
else if(maxEdgeWeight[j][i-1] > maxEdgeWeight[ancestor[j][i-1]][i-1] && 
secondMaxEdgeWeight[j][i] < maxEdgeWeight[ancestor[j][i-1]][i-1])
{
secondMaxEdgeWeight[j][i] = maxEdgeWeight[ancestor[j][i-1]][i-1];
}
}
}

// 寻找次小生成树
for(unsigned int i = 1; i <= edgeCount; i++)
{
if(edges[i].isInMST == 0)
{
unsigned int currentMax = findMaxInCycle(edges[i].from, edges[i].to, edges[i].weight);
if(currentMax != edges[i].weight && result > mstTotalWeight - currentMax + edges[i].weight)
{
result = mstTotalWeight - currentMax + edges[i].weight;
}
}
}

printf("%llu", result);
return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93413.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93413.shtml
英文地址,请注明出处:http://en.pswp.cn/web/93413.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Transformer浅说

rag系列文章目录 文章目录rag系列文章目录前言一、简介二、注意力机制三、架构优势四、模型加速总结前言 近两年大模型爆火&#xff0c;大模型的背后是transformer架构&#xff0c;transformer成为家喻户晓的词&#xff0c;人人都知道它&#xff0c;但是想要详细讲清楚&#x…

后台管理系统-3-vue3之左侧菜单栏和头部导航栏的静态搭建

文章目录1 CommonAside组件(静态搭建)1.1 Menu菜单1.2 准备菜单数据1.3 循环渲染菜单1.3.1 el-menu结构1.3.2 动态渲染图标1.4 样式设计1.5 整体代码(CommonAside.vue)2 CommonHeader组件(静态搭建)2.1 准备图片URL数据2.2 页面布局2.3 样式设计2.4 整体代码(CommonHeader.vue)…

VS Code配置MinGW64编译非线性优化库NLopt

VS Code用MinGW64编译C代码安装MSYS2软件并配置非线性优化库NLopt和测试引用库代码的完整具体步骤。 1. 安装MSYS2 下载安装程序&#xff1a; 访问 MSYS2官网下载 msys2-x86_64-xxxx.exe 并运行 完成安装&#xff1a; 默认安装路径&#xff1a;C:\msys64安装完成后&#xff0c…

C#通过TCP_IP与PLC通信

C#通过TCP/IP与PLC通信 本文将全面介绍如何使用C#通过TCP/IP协议与各种PLC进行通信&#xff0c;包括西门子、罗克韦尔、三菱等主流品牌PLC的连接方法。 一、PLC通信基础 PLC通信协议概览协议类型适用品牌特点Modbus TCP通用协议简单易用&#xff0c;广泛支持Siemens S7西门子PL…

Java 学习笔记(基础篇3)

1. 数组&#xff1a;① 静态初始化&#xff1a;(1) 格式&#xff1a;int[] arr {1, 2, 3};② 遍历/* 格式&#xff1a; 数组名.length */ for(int i 0; i < arr.length; i){//在循环的过程中&#xff0c;i依次表示数组中的每一个索引sout(arr[i]);//就可以把数组里面的每一…

知识点汇总linuxC高级-3 shell脚本编程

shell脚本编程shell ---> 解析器&#xff1a;sh csh ksh bashshell命令 ---> shell解析的命令shell脚本 --> shell命令的有序集合shell脚本编程&#xff1a;将shell命令结合按照一定逻辑集合到一起&#xff0c;写到一个 .sh 文件&#xff0c;去实现一个或多个功能&…

【C++学习篇】:基础

文章目录前言1. main() 函数2. 变量赋值3. cin和cout的一些细节4. 基本类型运算5. 内存占用6. 引用7. 常量前言 C 语法的学习整理&#xff0c;作为个人总结使用。 1. main() 函数 #include <iostream> //使用输入输出流库&#xff08;cin&#xff0c;cout&#xff09;…

使用nginx反向代理kkfile

这篇说一下我解决的思路和方式哈&#xff0c;不一定适用于大家&#xff0c;可以做个参考比如我们的系统服务是http://10.63.25.35:80&#xff0c;而我们的文件服务是在10.63.25.37:8012上&#xff0c;正常不使用代理的话&#xff0c;我们前端调用后端接口&#xff0c;后端调用k…

【低成本扩容】动态扩容实战指南

面对扩容操作时&#xff0c;下面这种操作是否也会迷惑你&#xff1f;下面来为大家解惑~size_t newcapacity 2*_capacity > (_size len)?2*_capacity:(_sizelen); //len为即将插入的字符串有效字符个数//_size为当前字符串有效字符个数//_capacity为当前容量大小//newcapa…

Product Hunt 每日热榜 | 2025-08-14

1. Autumn 标语&#xff1a;为AI初创公司简化的Stripe服务 介绍&#xff1a;Autumn帮助AI初创公司通过只需三个API调用来定价、计量和控制使用情况。基于Stripe搭建&#xff0c;它可以在一个地方管理订阅、使用情况和访问权限。无需复杂的webhooks或后端逻辑&#xff0c;非常…

Scrapy + Django爬虫可视化项目实战(二) 详细版

系列文章 Scrapy + Django爬虫可视化项目实战(一)_django scrapy-CSDN博客 实现技术 Scrapy Django Echarts 引言 可视化部分需要读者具备一定的Django基础!!! 上一个文章我们已经实现了爬取景点的数据,那么接下来就是根据爬取到的数据进行可视化 一、环境搭建 (一) 创…

选择式与生成式超启发算法总结

这里写目录标题Selection HHGeneration HHGPHH示例存在大量针对特定问题设计的启发式算法&#xff0c;近年来学术界提出了一个关键问题&#xff1a;如何选择最合适的启发式方法。这一问题推动了超启发式&#xff08;hyper-heuristic&#xff09;方法的研究发展。超启发式是一种…

NetBIOS 设置

在 Windows 系统中,WINS (Windows Internet Name Service) 和 NetBIOS 紧密相关,主要用于 NetBIOS 名称解析(将计算机名转换为 IP 地址)。WINS 是一个动态数据库,类似于 DNS,但专门用于 NetBIOS 名称解析,适用于早期 Windows 网络(如 Windows NT/2000/XP)。 1. 查看 N…

vue2 + SimpleMindMap 制作思维导图

vue2 SimpleMindMap 制作思维导图 该代码包含SimpleMindMap已知的所有功能&#xff0c;有需要的小伙伴可自行copy&#xff0c;框架使用el-ementui。其中有些图标是阿里巴巴矢量图的图片&#xff0c;可自行进行替换。保姆级教程 以下是vue文件&#xff1a; <template><…

Discord x Pulsar: 使用 Pulsar、Flink 和 Iceberg 搭建流式机器学习平台

本文整理自 Discord 机器学习工程师 David Christle 在 Pulsar Summit NA 上的演讲内容&#xff0c;一起来看 Discord 是如何基于 Pulsar 实现兼顾安全和个性化功能的实时流式机器学习平台的&#xff5e;1. 背景Discord 是一个实时⾳视频通信平台&#xff0c;⽀持⽂本/语⾳/视频…

【数据结构入门】二叉树(2)

目录 1.二叉树遍历顺序 1.1 前序&#xff08;先根&#xff09;遍历 1.2 中序&#xff08;中根&#xff09;遍历 1.3 后序&#xff08;后根&#xff09;遍历 1.4 层序遍历 1.5 深度优先遍历&广度优先遍历 2.二叉树的遍历 2.1 前根遍历&#xff08;递归&#xff09; …

【电机参数】电压、电流、转速标幺化推算过程

【电机参数】电压、电流、转速标幺化推算过程 文章目录[TOC](文章目录)前言一、标幺化目的——优化计算二、Q15与标幺化的关系三、标幺值计算1.电压标幺值2.电流标幺值3.转速标幺值四、参考资料总结前言 一、标幺化目的——优化计算 不同物理量的量纲和数值范围差异巨大&#…

v-scale-scree: 根据屏幕尺寸缩放内容

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

linux设备驱动之字符设备驱动

一、cdev结构体‌成员/功能‌‌说明‌‌相关操作函数/宏‌‌kobj‌内嵌的kobject对象&#xff0c;用于Linux设备模型管理&#xff0c;实现引用计数和sysfs接口kobject_init()‌owner‌指向拥有该结构体的模块指针&#xff08;通常为THIS_MODULE&#xff09;&#xff0c;防止模块…

★CentOS:MySQL数据备份

一、cp 命令备份特点&#xff1a;优点&#xff1a;备份恢复数据快&#xff1a;直接复制文件&#xff0c;无需进行数据转换和复杂的处理&#xff0c;因此备份恢复速度非常快缺点&#xff1a;需要停止数据库服务&#xff0c;灵活性差&#xff0c;占用空间大&#xff0c;可移植性差…