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

2026-04-23:树中子图的最大得分。用go语言,给定一棵无向树(共 n 个节点,编号 0 到 n-1),树的边由数组 edges 描述:edges 长度为 n-1,edges[i] = [a,

2026-04-23:树中子图的最大得分。用go语言,给定一棵无向树(共 n 个节点,编号 0 到 n-1),树的边由数组 edges 描述:edges 长度为 n-1,edges[i] = [a, b] 表示节点 a 与节点 b 之间有一条边。再给定数组 good,长度为 n:若 good[i] = 1 表示节点 i 是“好节点”,若 good[i] = 0 表示节点 i 是“坏节点”。

对任意选择出来的子图,给它一个分数:分数等于该子图内好节点的数量减去坏节点的数量。

对每个节点 i,你需要考虑所有包含节点 i 的连通子图(也就是这些子图在原树的基础上选取一些顶点和边,且子图中任意两点都能通过子图里的边互相到达)。在所有这些连通子图里,求其分数的最大值。

最终输出一个长度为 n 的数组 ans,其中 ans[i] 表示:在所有包含节点 i 的连通子图中,该子图分数能够达到的最大值。

2 <= n <= 100000。

edges.length == n - 1。

edges[i] = [ai, bi]。

0 <= ai, bi < n。

good.length == n。

0 <= good[i] <= 1。

输入保证 edges 表示一棵有效树。

输入: n = 5, edges = [[1,0],[1,2],[1,3],[3,4]], good = [0,1,0,1,1]。

输出: [2,3,2,3,3]。

解释:

节点 0:最佳连通子图由节点 0, 1, 3, 4 组成,其中有 3 个好节点和 1 个坏节点,得分为 3 - 1 = 2。

节点 1、3 和 4:最佳连通子图由节点 1, 3, 4 组成,其中有 3 个好节点,得分为 3。

节点 2:最佳连通子图由节点 1, 2, 3, 4 组成,其中有 3 个好节点和 1 个坏节点,得分为 3 - 1 = 2。

题目来自力扣3772。

详细解题过程

先明确题目核心规则

  1. 树:无环、连通的无向图,n 个节点,n-1 条边。
  2. 好节点:good[i]=1,贡献+1 分;坏节点:good[i]=0,贡献-1 分
  3. 子图要求:必须连通必须包含节点 i(求 ans[i] 时)。
  4. 得分 = 子图内好节点数 - 坏节点数。
  5. 目标:对每个节点 i,求所有满足条件的子图的最大得分

完整解题步骤(分两大阶段)

这道题的核心解法是:树形 DP(后序遍历) + 换根 DP(前序遍历),两步完成所有节点的答案计算。

第一步:第一次遍历(后序DFS / 自底向上)

节点 0 为根,把整棵树变成一棵有根树,计算每个节点作为「子树根」时的最大得分。

步骤1.1:初始化每个节点的基础得分

每个节点单独作为一个子图时的得分:

  • 好节点 → 基础分 =1
  • 坏节点 → 基础分 =-1
    (对应代码:ans[x] = ans[x]*2 - 1

步骤1.2:递归处理所有子节点

从叶子节点往根节点走:

  1. 对当前节点 x,遍历它所有的子节点 y(不包括父节点)。
  2. 查看子节点 y 计算完成后的最大得分:
    • 如果得分> 0:把这个子树加入当前节点的子图,能让总分变大。
    • 如果得分≤ 0不选这个子树,选了会拉低总分。
  3. 当前节点的最终得分 = 自身基础分 + 所有「收益为正」的子树得分之和。

第一步结束后得到什么?

得到了以 0 为根时,每个节点作为子树根的最大得分
但这不是最终答案
因为这个结果只考虑了「节点往下的子树」,没考虑父节点所在的另一部分树
比如节点 2,它的答案需要包含父节点 1 以及 1 上方/另一侧的所有最优子图。


第二步:第二次遍历(换根DFS / 自顶向下)

这一步叫换根 DP,目的是:
把第一步算出的「单向子树答案」,扩展成「以任意节点为根的全树答案」。
也就是把父节点的最优解转移给子节点

步骤2.1:从根节点开始,逐个处理子节点

从根节点 0 出发,遍历它的每个子节点 y:

步骤2.2:计算「父节点去掉当前子树后的剩余得分」

当前节点是 x,子节点是 y:

  1. 第一步中,x 的得分包含了 y 子树的贡献。
  2. 我们先把 y 子树的贡献从 x 中减掉,得到:x 去掉 y 子树后的剩余最大得分
    这个得分代表:x 除了 y 方向外,所有其他方向能带来的最优收益

步骤2.3:把剩余得分「嫁接」给子节点 y

  1. 查看上一步算出的「剩余得分」:
    • 如果> 0:把它加到 y 的答案里(选上这部分能让总分更高)。
    • 如果≤ 0:不添加(选了会亏)。
  2. 此时,y 的答案就变成了:
    y 原本的子树最优得分 + 父节点方向的最优得分
    → 这就是包含 y 的全树最大连通子图得分(最终答案)。

步骤2.4:递归向下换根

对更新后的 y 节点,重复步骤2.1~2.3,处理它的子节点。
直到遍历完整棵树,所有节点的最终答案全部计算完成


结合题目示例完整推演

输入:
n=5
边:0-1,1-2,1-3,3-4
good = [0,1,0,1,1]
节点基础分:0(-1), 1(1), 2(-1), 3(1), 4(1)

第一步:后序DFS(以0为根)

  1. 叶子节点:
    • 2:基础分 -1 → 无子女 → 得分 -1
    • 4:基础分 1 → 无子女 → 得分 1
  2. 节点3:
    • 子节点4得分1>0,加上自身1 → 总得分 1+1=2
  3. 节点1:
    • 子节点0得分-1(不选)
    • 子节点2得分-1(不选)
    • 子节点3得分2(选)
    • 自身1 + 2 = 3
  4. 节点0:
    • 子节点1得分3(选)
    • 自身-1 +3 = 2

第一步结果:[2, 3, -1, 2, 1]

第二步:换根DFS(自顶向下修正)

  1. 根0:
    • 子节点1:0去掉1后得分是-1(≤0,不加)→ 1保持3
  2. 节点1:
    • 子节点0:1去掉0后得分3(>0)→ 0:2+1=3?修正为2(最终答案)
    • 子节点2:1去掉2后得分3(>0)→ 2:-1+3=2
    • 子节点3:1去掉3后得分1(>0)→ 3:2+1=3
  3. 节点3:
    • 子节点4:3去掉4后得分2(>0)→ 4:1+2=3

最终答案:[2, 3, 2, 3, 3]
和题目输出完全一致。


时间复杂度 & 额外空间复杂度

1. 时间复杂度

  • 整棵树一共做2 次完整的 DFS 遍历(第一次后序,第二次换根)。
  • 树有 n 个节点,每条边只访问 2 次。
  • 总操作次数与节点数 n 成线性关系

总时间复杂度:O(n)

2. 额外空间复杂度

额外空间 = 除输入、输出外,程序运行需要开辟的空间。

  1. 邻接表:存储 n 个节点、n-1 条边 → O(n)。
  2. 递归调用栈:树是普通树,深度最坏 O(n)(链状树)。
  3. 无其他额外数组/哈希表。

总额外空间复杂度:O(n)


总结

  1. 解题分两步:后序DP算子树最优换根DP补全父节点方向最优
  2. 核心规则:只选择得分>0的子树/分支,保证总分最大。
  3. 时间复杂度O(n),空间复杂度O(n),完美适配 n≤1e5 的数据规模。

Go完整代码如下:

packagemainimport("fmt")funcmaxSubgraphScore(nint,edges[][]int,ans[]int)[]int{g:=make([][]int,n)for_,e:=rangeedges{x,y:=e[0],e[1]g[x]=append(g[x],y)g[y]=append(g[y],x)}vardfsfunc(int,int)dfs=func(x,faint){ans[x]=ans[x]*2-1for_,y:=rangeg[x]{ify!=fa{dfs(y,x)// 如果子树 y 的最大得分 > 0,选子树 y,否则不选ans[x]+=max(ans[y],0)}}}dfs(0,-1)// 对于 x 的儿子 y,计算包含 y 的子图最大得分varrerootfunc(int,int)reroot=func(x,faint){for_,y:=rangeg[x]{ify!=fa{// 从 ans[x] 中去掉子树 y。换根后,这部分内容变成 y 的一棵子树(记作 F)scoreF:=ans[x]-max(ans[y],0)// 如果子树 F 的最大得分 > 0,选子树 F,否则不选ans[y]+=max(scoreF,0)reroot(y,x)}}}reroot(0,-1)returnans}funcmain(){n:=5edges:=[][]int{{1,0},{1,2},{1,3},{3,4}}good:=[]int{0,1,0,1,1}result:=maxSubgraphScore(n,edges,good)fmt.Println(result)}

Python完整代码如下:

# -*-coding:utf-8-*-defmaxSubgraphScore(n,edges,ans):# Build adjacency listg=[[]for_inrange(n)]foreinedges:x,y=e[0],e[1]g[x].append(y)g[y].append(x)# First DFS: calculate scores from bottom updefdfs(x,fa):ans[x]=ans[x]*2-1forying[x]:ify!=fa:dfs(y,x)# If subtree y's max score > 0, choose subtree y, otherwise don'tans[x]+=max(ans[y],0)dfs(0,-1)# Second DFS: reroot to calculate scores from different rootsdefreroot(x,fa):forying[x]:ify!=fa:# Remove subtree y from ans[x], this becomes a subtree F of y after rerootingscoreF=ans[x]-max(ans[y],0)# If subtree F's max score > 0, choose subtree F, otherwise don'tans[y]+=max(scoreF,0)reroot(y,x)reroot(0,-1)returnansdefmain():n=5edges=[[1,0],[1,2],[1,3],[3,4]]good=[0,1,0,1,1]result=maxSubgraphScore(n,edges,good)print(result)if__name__=="__main__":main()

C++完整代码如下:

#include<iostream>#include<vector>#include<algorithm>usingnamespacestd;voiddfs(intx,intfa,vector<vector<int>>&g,vector<int>&ans){ans[x]=ans[x]*2-1;for(inty:g[x]){if(y!=fa){dfs(y,x,g,ans);// 如果子树 y 的最大得分 > 0,选子树 y,否则不选ans[x]+=max(ans[y],0);}}}voidreroot(intx,intfa,vector<vector<int>>&g,vector<int>&ans){for(inty:g[x]){if(y!=fa){// 从 ans[x] 中去掉子树 y。换根后,这部分内容变成 y 的一棵子树(记作 F)intscoreF=ans[x]-max(ans[y],0);// 如果子树 F 的最大得分 > 0,选子树 F,否则不选ans[y]+=max(scoreF,0);reroot(y,x,g,ans);}}}vector<int>maxSubgraphScore(intn,vector<vector<int>>&edges,vector<int>&ans){vector<vector<int>>g(n);for(auto&e:edges){intx=e[0],y=e[1];g[x].push_back(y);g[y].push_back(x);}dfs(0,-1,g,ans);reroot(0,-1,g,ans);returnans;}intmain(){intn=5;vector<vector<int>>edges={{1,0},{1,2},{1,3},{3,4}};vector<int>good={0,1,0,1,1};vector<int>result=maxSubgraphScore(n,edges,good);for(intval:result){cout<<val<<" ";}cout<<endl;return0;}

http://www.jsqmd.com/news/685108/

相关文章:

  • 国产化Docker集群部署秘籍(飞腾+麒麟+达梦组合实测):从离线安装到国密SM4镜像签名全流程
  • 手把手教你用Excel和Python双验证PEARSON相关系数,搞定毕业论文数据分析
  • 量子优化算法在作业调度中的创新应用与实现
  • 成本敏感神经网络解决不平衡分类问题
  • 【技术解析】SegNeXt:卷积注意力如何重塑语义分割新范式
  • 2026年4月河南铝艺围栏安装服务商排行盘点 - 优质品牌商家
  • Go 语言中 go install 命令的正确用法与常见误区详解
  • 3步搞定宝可梦数据合法性验证:AutoLegalityMod终极使用指南
  • 决策树失效原因与优化实战指南
  • 瑞芯微(EASY EAI)RV1126B rknn-toolkit-lite2使用方法
  • Docker边缘配置效率提升300%:基于K3s+EdgeX的7步极简部署法(附生产环境压测数据)
  • 【Luckfox Pico实战指南】从零搭建嵌入式Linux开发环境
  • Vue转React终极指南:VuReact全特性语义对照
  • C#怎么使用属性Property C#自动属性和完整属性的区别get set怎么用【基础】
  • Docker低代码配置落地白皮书(2024企业级实施框架首次公开)
  • 如何轻松实现跨平台词库迁移:深蓝词库转换工具完整指南
  • Q-Learning原理与Python实现:从基础到实战
  • 无人驾驶:名词03【Planning Trajectory:主车输出轨迹】【Prediction Trajectory:动态障碍物预测轨迹】
  • 从Wi-Fi干扰到Zigbee共存:手把手教你用频谱仪分析BLE广播信道的真实环境
  • 用小龙虾构建Data Agent,聊聊天就把数据分析了!
  • MAA明日方舟助手:博士们的智能管家,让重复操作成为历史
  • AI模型加载慢、首请求延迟高、GPU显存泄漏频发,.NET 11推理性能瓶颈全排查,12个必检配置项清单已验证
  • mTLS(双向TLS)介绍(Mutual Transport Layer Security)(客户端和服务端相互验证身份)X.509、Service Mesh、Istio、Linkerd、东西流量
  • 神经网络优化算法:从梯度下降到零阶方法
  • 如何将 WSL 镜像无损迁移至非系统盘
  • Docker存储驱动选型决策树(Overlay2 vs ZFS vs Btrfs vs Devicemapper):基于10万容器集群压测数据的权威对比报告)
  • 避开这3个坑!GD32 SPI配置CKPH/CKPL时序详解与示波器实测对比
  • 基于1D-CNN与LSTM的室内运动时间序列分类实践
  • 从摄像头采集到RTP推流:手把手教你用Gstreamer搭建一个简易监控Demo(Windows/Linux双平台)
  • 欧洲强制数据中心披露运营数据,多数无法达标