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

【算法竞赛/大厂面试】3600. 升级后最大生成树稳定性解析

题目描述

给你一个整数n,表示编号从 0 到 n - 1 的n个节点,以及一个edges列表,其中edges[i] = [ui, vi, si, musti]

  • uivi表示节点uivi之间的一条无向边。

  • si是该边的强度。

  • musti是一个整数(0 或 1)。如果musti == 1,则该边必须包含在生成树中,且不能升级

你还有一个整数k,表示你可以执行的最多升级次数。每次升级会使边的强度翻倍,且每条可升级边(即musti == 0)最多只能升级一次。

一个生成树的稳定性定义为其中所有边的最小强度。

返回任何有效生成树可能达到的最大 稳定性。如果无法连接所有节点,返回-1

生成树 (Spanning Tree)的定义:

  1. 连接所有节点(连通)。

  2. 不形成任何环。

  3. 恰好包含n - 1条边。

示例

示例 1:

输入:n = 3, edges = [[0,1,2,1],[1,2,3,0]], k = 1

输出:2

解释:

[0,1]强度为 2,必须包含。

[1,2]是可选的,可以升级一次,强度变为 6。

最终生成树的边强度为 2 和 6。最小强度为 2,这是最大可能的稳定性。

示例 2:

输入:n = 3, edges = [[0,1,4,0],[1,2,3,0],[0,2,1,0]], k = 2

输出:6

解释:

所有边都是可选的。升级[0,1]到 8,[1,2]到 6。

生成树选这两条边,最小强度为 6。

示例 3:

输入:n = 3, edges = [[0,1,1,1],[1,2,1,1],[2,0,1,1]], k = 0

输出:-1

解释:所有边都是必选的,构成环,无法形成生成树。

提示

  • 2 <= n <= 10^5

  • 1 <= edges.length <= 10^5

  • 1 <= si <= 10^5

  • 0 <= k <= n

  • 无重复边。


问题解析与核心思路

1. 问题核心

我们需要在满足生成树定义的前提下,最大化生成树的“稳定性”。稳定性 = 生成树中所有边强度的最小值。

要最大化这个最小值,一个经典的策略是“二分答案 + 检验”(Binary Search on Answer)。

2. 关键洞察

  1. 必选边 (musti=1) 的处理

    • 首先,必须将所有musti=1的边加入生成树。

    • 加入这些边后,我们需要检查:

    a. 是否形成了环?如果形成环,直接返回-1

    b. 图是否被分成了若干个连通块(连通分量)?

    • 此时,问题转化为:我们有若干个连通块,需要用可升级边 (musti=0)去连接它们。我们的目标是,在使用不超过k次升级的前提下,选择一组边,将所有连通块连通,并让所选边中最小的强度尽可能大。
  2. 可升级边 (musti=0) 的处理

    • 每条可升级边可以选择不升级升级 1 次(强度si * 2)。

    • 我们的目标是选择一组边,连接所有连通块,且这组边中强度的最小值最大。

    • 为了最大化最小值,我们应该优先选择强度大的边。

3. 算法框架

基于以上分析,我们可以采用Kruskal 算法结合二分答案的思路:

  1. 预处理必选边

    • 使用并查集 (Union-Find / Disjoint Set Union, DSU) 数据结构,将所有musti=1的边加入。

    • 如果加入过程中发现环,返回-1

    • 记录此时图中的连通块数量components

  2. 二分答案

    • 我们要找的最大稳定性ans必然在某个边的强度(或其升级后的值)中。

    • 设定二分查找的左边界left = 1,右边界right = max_s * 2(max_s为所有边的最大强度,因为最多升级一次)。

    • [left, right]范围内进行二分。对于中间值mid,我们需要检验:**是否存在一种选择方案,使得生成树的稳定性至少为 **mid**,且升级次数不超过 **k

  3. 检验函数 (Check Function)

    • 给定一个目标稳定性mid,我们需要判断能否构造一个生成树:

    a. 所有边的强度都 **至少为 **mid

    b. 恰好使用n-1条边。

    c. 连通所有节点。

    d. 使用的升级次数used_k <= k

    • 如何检验?

      1. 再次使用并查集。

      2. 优先选择必选边。如果必选边的强度小于mid,则无法满足条件,直接返回False

      3. 然后,我们需要用可升级边来连接剩余的连通块。我们分两步处理可升级边:

        • 第一步 (零成本边):选择强度s >= mid的可升级边,不升级。用这些边尽可能多地连通块,消耗 0 次升级。

        • 第二步 (有成本边):选择强度s < mids*2 >= mid的可升级边,升级一次。用这些边继续连通块,消耗 1 次升级。

      4. 最后,检查是否连通了所有节点,且消耗的升级次数used_k是否小于等于k


代码实现 (Python)

import sys from typing import List # 并查集 (DSU) 类 class DSU: def __init__(self, size: int): self.parent = list(range(size)) self.rank = [1] * size def find(self, x: int) -> int: if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, x: int, y: int) -> bool: x_root = self.find(x) y_root = self.find(y) if x_root == y_root: return False # 已在同一集合,形成环 # 按秩合并 if self.rank[x_root] < self.rank[y_root]: self.parent[x_root] = y_root else: self.parent[y_root] = x_root if self.rank[x_root] == self.rank[y_root]: self.rank[x_root] += 1 return True class Solution: def maxStability(self, n: int, edges: List[List[int]], k: int) -> int: # 修正函数名为maxStability,匹配题目要求,解决AttributeError报错 # 存储中间输入变量,按题目要求 drefanilok = (n, edges, k) # 步骤 1: 处理必选边 (musti=1) dsu_mandatory = DSU(n) has_cycle = False max_original_s = 0 mandatory_edges = [] optional_edges = [] for u, v, s, must in edges: max_original_s = max(max_original_s, s) if must == 1: # 必选边必须加入,若形成环则无解 if not dsu_mandatory.union(u, v): has_cycle = True mandatory_edges.append( (u, v, s) ) else: optional_edges.append( (u, v, s) ) if has_cycle: return -1 # 计算初始连通块数量 components = 0 for node in range(n): if dsu_mandatory.find(node) == node: components += 1 # 如果初始已连通 (components == 1),则稳定性就是最小边强度 if components == 1: min_s = float('inf') for u, v, s, must in edges: if must == 1: # 必选边必须包含,其强度是最小值的候选 min_s = min(min_s, s) return min_s # 步骤 2: 二分答案 left = 1 right = max_original_s * 2 # 最大可能的强度是原最大边翻倍 answer = -1 def is_possible(target_stability: int, upgrade_remaining: int) -> bool: # 检验是否能达到 target_stability,且升级次数不超过 upgrade_remaining dsu_check = DSU(n) # 1. 先加入所有必选边,若其强度不足,则直接不行 for u, v, s in mandatory_edges: if s < target_stability: return False dsu_check.union(u, v) used_upgrades = 0 remaining_components = components # 初始连通块数 # 2. 优先选择可升级边,分两步: # a. 不升级,强度 >= target 的边 (0 cost) # b. 升级一次,强度*2 >= target 的边 (1 cost) # 先处理 a 类边 (0 cost) for u, v, s in optional_edges: if s >= target_stability: if dsu_check.union(u, v): remaining_components -= 1 if remaining_components == 1: break # 已连通,提前退出 if remaining_components == 1: return used_upgrades <= upgrade_remaining # 再处理 b 类边 (1 cost) for u, v, s in optional_edges: if s * 2 >= target_stability and s < target_stability: # 确保是需要升级才能达标的 if used_upgrades >= upgrade_remaining: break # 升级次数用尽 if dsu_check.union(u, v): used_upgrades += 1 remaining_components -= 1 if remaining_components == 1: break # 最终检查 return remaining_components == 1 and used_upgrades <= upgrade_remaining # 二分主循环 while left <= right: mid = (left + right) // 2 if is_possible(mid, k): # 可行,尝试更大的稳定性 answer = mid left = mid + 1 else: # 不可行,尝试更小的稳定性 right = mid - 1 return answer # 为了在CSDN文档中符合示例代码格式,单独定义主函数逻辑 if __name__ == "__main__": # 示例 1 测试 sol = Solution() n1, edges1, k1 = 3, [[0,1,2,1],[1,2,3,0]], 1 print(sol.maxStability(n1, edges1, k1)) # 同步修改测试函数名,避免报错 # 示例 2 测试 n2, edges2, k2 = 3, [[0,1,4,0],[1,2,3,0],[0,2,1,0]], 2 print(sol.maxStability(n2, edges2, k2)) # 同步修改测试函数名 # 示例 3 测试 n3, edges3, k3 = 3, [[0,1,1,1],[1,2,1,1],[2,0,1,1]], 0 print(sol.maxStability(n3, edges3, k3)) # 同步修改测试函数名

代码解析与复杂度分析

1. 并查集 (DSU)

  • 作用:高效管理和合并连通分量,检测环。

  • 操作find(带路径压缩) 和union(按秩合并)。

  • 均摊时间复杂度:几乎是常数级O(α(N)),其中α是阿克曼函数的反函数,增长极慢。

2. 二分查找

  • 范围[1, max_s * 2]

  • 次数log2(2 * 10^5) ≈ 18次。

3. 检验函数 (is_possible)

  • 时间复杂度O(E),其中E是边的数量。

  • 逻辑

    • 遍历必选边,检查合法性。

    • 分两次遍历可升级边,尝试连接连通块。

4. 整体时间复杂度

  • O(E * log(max_s))

  • 对于nedges达到10^5的数据规模,此复杂度是完全可以接受的。

5. 空间复杂度

  • O(n + E),主要用于存储并查集的数据结构和边列表。

总结与扩展

总结

本题的核心在于将“最大化最小值”的经典问题与图论中的生成树、并查集结合,并引入了“边升级”这一带有成本的操作。

  1. 第一步是处理必选边,这是硬性约束,必须优先满足。

  2. 第二步是使用二分答案来锁定目标稳定性。

  3. 第三步是设计检验逻辑,在保证目标稳定性的前提下,用最少的升级次数连通所有图。

扩展思考

  1. 升级次数更多 (k** 很大)**:如果k非常大,比如大于等于可选边的数量,那么我们可以将所有可选边都升级一次。此时问题就退化为一个普通的“最大生成树”问题(Kruskal 算法,选最大边)。

  2. 升级多次:如果题目允许对同一条边升级多次(强度si * 2^m),则问题的二分范围会变大,检验逻辑也需要调整,以适应不同升级次数的成本。

  3. 其他类型边:如果边有不同类型的升级方式(如固定数值增加、随机加成),则策略会更加复杂,但核心的“二分答案 + 检验”框架依然适用。

这份解析和代码希望能帮助你彻底理解这道题的解法和思路。

http://www.cnnetsun.cn/news/1250126.html

相关文章:

  • vdhcoapp:3大技术突破打造跨平台视频捕获解决方案
  • ssm+java2026年毕设社区生鲜电商平台【源码+论文】
  • 解决云顶之弈决策效率问题的TFT Overlay全场景应用指南
  • JavaScript代码还原工具实战指南:零基础上手AST反混淆技术
  • 汇爱家 ai 学习智能体做什么的
  • 阿洲旧房翻新领衔 肇庆正规二手房翻新公司推荐
  • Sci Immunol.(IF=16.3)|单细胞图谱揭秘:GPR25如何塑造肺肝“常驻”T细胞,增强抗肿瘤免疫!
  • G-Star Gathering Day 武汉站报名开启!
  • 3大核心功能让你的英雄联盟体验全面升级:League Akari智能助手深度评测
  • PTA 串的算法设计 5 BF匹配算法(单次匹配)
  • AI编程能力边界探索:基于 Claude Code 的 Spec Coding 项目实战|得物技术
  • 告别论文焦虑:Paperxie 降重 + 降 AIGC 双效方案,让学术写作更从容
  • 突破游戏本性能桎梏:OmenSuperHub的智能调控技术革命
  • 【面试核心】Spring Boot 高频考点全解析(整合版)
  • 文件操作(一)
  • Xilinx AXI UART Lite IP核实战仿真例程(非example)
  • RabbitMq高级篇
  • 企业AI大脑是什么?企业落地前先回答的 5 个关键问题
  • 干货合集:AI论文工具,专科生专属!千笔AI VS 知文AI
  • 复合文件工具
  • 4步实现高效直播内容保存:面向内容创作者的抖音直播下载与管理工具
  • Buildozer:Python跨平台应用打包工具实战指南
  • 2026 3 12 前端学习
  • Matlab与Simulink联合仿真验证车辆运动学模型:以车速和前轮转角为输入,对比Cars...
  • 解决git重复提交历史记录 的问题
  • 金三银四网络安全求职全攻略:抓住327万人才缺口,精准斩获高薪Offer
  • 考虑阶梯式碳交易机制与电制氢的综合能源系统热电优化 关键词:碳交易 电制氢 阶梯式碳交易 综合...
  • 精准对接消费升级,织造行业开启“智变”新征程
  • 哈曼曲线的分析及不同设备运用
  • 分享|聊一聊AIGC应用工程师报考|抢占“技术+业务”复合型人才新风口