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

《P4180 [BJWC2010] 严格次小生成树》

题目描述

小 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.lryc.cn/news/622872.html

相关文章:

  • [极客时间]LangChain 实战课 ----- 代理(上)|(12)ReAct框架,推理与行动的协同
  • Manus AI与多语言手写识别的技术突破与行业变革
  • 《Python学习之字典(一):基础操作与核心用法》
  • 【每日一题】Day5
  • 电路设计——复位电路
  • 设计模式之静态代理
  • Java 10 新特性及具体应用
  • ABB焊接机器人弧焊省气
  • 多机编队——(6)解决机器人跟踪过程中mpc控制转圈问题
  • 【轨物方案】预防性运维:轨物科技用AI+机器人重塑光伏电站价值链
  • MyBatis极速通关中篇:核心配置精讲与复杂查询实战
  • 大模型教机器人叠衣服:2025年”语言理解+多模态融合“的智能新篇
  • Tomcat架构深度解析:从Server到Servlet的全流程揭秘
  • blender制作动画导入unity两种方式
  • ENSP的简单动态路由rip协议配置
  • 广东省省考备考(第七十八天8.16)——资料分析、判断推理(强化训练)
  • Docker目录的迁移
  • GaussDB 数据库架构师修炼(十三)安全管理(3)-行级访问控制
  • 6JSON格式转python并实现数据可视化
  • 在ubuntu系统上离线安装jenkins的做法
  • 零基础学习人工智能的完整路线规划
  • Flink Stream API 源码走读 - window 和 sum
  • (第十七期)HTML图像标签详解:从入门到精通
  • 【完整源码+数据集+部署教程】高尔夫球追踪与识别系统源码和数据集:改进yolo11-LAWDS
  • 【基础-判断】可以通过ohpm uninstall 指令下载指定的三方库
  • 力扣(接雨水)——基于最高柱分割的双指针
  • 【开发技巧】VS2022+QT5+OpenCV4.10开发环境搭建QT Creator
  • 肖臻《区块链技术与应用》第23-26讲 - The DAO事件、BEC事件、反思和总结
  • Qt 关于QString和std::string数据截断的问题- 遇到\0或者0x00如何处理?
  • ★CentOS:MySQL数据备份