树形dp
树形背包
树形背包类问题的模型一般是选择不超过 个点求最值,通常会出现诸如选儿子必须先选父亲的条件,因此,对于每一个节点的子树而言,每一个儿子相当于分组背包的一个组,而像下图不同颜色标注的选取部分相当于每组中可选的物品。
从一道例题说起:
很明显,选择的课程个数相当于物品,而先修的规则满足树形背包的要求。
按照一般套路,设 表示以 为根的子树中选修 门课获得的最大学分是多少
转移类似于分组背包,枚举儿子 相当于枚举组,第二维倒序枚举容量,第三位枚举物品,则
好的,目前时间复杂度为
再来考虑优化:
观察代码,发现枚举背包容量的时候每次都要枚举到 ,这其实非常浪费的,因为有可能当前子树很小,则容许的背包容量也很小。
于是可以只枚举到 即可
关于时间复杂度:总之这样是 的,至于为什么——简单理解一下因为每个点对都只在LCA 处计算一次
对于背包容量上限较大的题,更严格的复杂度是
这几道题很类似且较为简单:
P1272 重建道路
P1270 “访问”美术馆
CF815C Karen and Supermarket
一年前曾经做过然而现在又不会了……
直接算点对肯定不好算,那么考虑每条边权对答案的贡献
那么只要知道边两端黑白点个数分别是多少便可以
由于黑点总数已知,那么只需要记录黑点个数,便可以知道其他所有信息
设 表示子树内共有 个黑点的总和,树形背包转移即可
下面就是难一点不会做的题了
首先,放置监听器数量的严格固定,相当于背包容量
这题有一个非常特殊的限制——“放置在节点 u 的监听设备并不监听 u 本身的通信”,于是对于一个点的状态并不只受当前点放不放的控制
另一个关键点是因为管辖半径为1,那么对于节点 u 来说,如果儿子没被监听,u也不监听,那么以后就没有机会了,所以对于 u 节点的子树,除 u 节点外其他节点必须已经被监听
于是设计状态: 表示节点 u 的子树共使用 j 个监听器,u 节点是否放置, u 节点是否被监听,且此时其他点都被监听的方案数
由于涉及组合数学,可以考虑直接按照优化后的树形 dp 来写会更方便。
于是 4 种情况分别转移:
注意这道题 m 很小,而 很大,需要取
和上道题比起来,数据范围友好了许多,但是有一个很难受的是价值的计算,为到最近伐木场的距离,这样以来一个节点的状态不再之和父亲、儿子有关,而是需要关联到很远的祖先
状态为:表示u节点最近的建场祖先是 j 节点,子树内共建场k个,u建没建的最小代价
由于这道题节点数量本身很小,可以直接枚举祖先
转移:
注意这里,相当于把 向 合并,方便下一层的转移
换根
对于树的方向(即根的不同)对答案产生影响的 ,往往可以通过换根 来求出每个点为根的答案
换根的过程关键在于消除子树影响
对于最值问题的 通常可以记录子树的最小值和次小值的方式来实现
对于加和之间减去,乘积问题可以处理逆元,比如 B. 树论
对于一些朴素的换根 ,可以使用 up and down 的方式简单书写
比如树上最短路,可以直接向上取 ,向下取 即可,这种 和 都不会算重不会产生问题
树形 的优化
树形 的第二维常常与子树大小、深度有关,那么采用启发式合并的思路直接把重(长)儿子的信息继承过来复杂度就可以优化为 级别
可以发现在这种情境下树内的物品没有限制关系,是随意选的,那么可以发现第二维越大得到的新贡献就越小,因此 的差分数组是单调的
把差分放进堆里,利用堆 的 特性复杂度就变得正确了
具体见 这里
注意这道题并不能长剖,因为要求求出每个的值
对于一种特殊的 ,其每个节点上的合法状态都是一些区间集合,并且叶节点区间个数为 ,这样的 如果合并过程中保证每个节点的区间都是没有包含关系的话,复杂度也是 的
其他神奇的树形dp
上下分别维护
一些题中需要统计树上满足条件的路径数,并且路径具有一定的方向性
那么可以 出一个点子树向上路径和向下路径分别的 值,在当前节点进行拼接
神奇的 题
首先发现作为起点是独特的,贡献为周围点权和,其他地方都需要减去父亲节点的权值
那么需要区分起点和终点,可以设 表示以 为根的子树里从某个点为起点到点 的路径撒了 次的最大收获
表示从 开始到从 到以 为根子树内一点作为终点的的路径撒了 次的最大收获
那么更新答案为 ,考虑更新:
初始化为 ,
实现的时候还有一个问题:由于一个是起点另一个是终点,更新答案是根据儿子的顺序来更新的,那么遍历的顺序是有影响的,还需要倒的扫一遍
这道题的巧妙之处更在于条件的转化
可以每种颜色分开考虑:一种颜色的贡献是 包含这种颜色至少1个点的路径条数
继续转化,一般情况“至少为1”这样的条件不好处理,可以利用补集思想用总路径条数减去没有出现这种颜色点的路径数
首先来看暴力算法:
因为固定颜色后是这种颜色的点不能经过,于是这种颜色的点将整棵树分割成若干个连通块,每个连通块中所有路径均满足条件,贡献 tot*(tot-1)/2 条路径
时间复杂度
再来优化,首先可以将形式变为树形 , 开一个二维数组放颜色,这样就改成了普通 dp 的样子
于是现在优化以后时间空间都过不去了
再来考虑,因为一个点只有一种颜色,所以除了这种颜色以外其他颜色的转移都是在浪费时间,于是只考虑当前颜色即可,解决了时间问题
因为子树的连续性以及分割,每种颜色没必要每个点开一个,开成全局记录即可,对于当前连通块的统计可以用子树总大小减去被截出去的部分,解决了空间问题
于是解决了这道题
首先要解决一个问题是怎样求一个子树的重心
一个比较方便的做法是一直走重儿子,直到其子树大小小于等于总点数一半
但是如果暴力跳会非常慢,可以考虑倍增, 表示节点 倍重儿子是谁
再考虑统计答案
从根节点出发,每次将一个节点作为根,其与一个儿子的连边作为答案统计边,然后分别求出以 为根的子树和以 为根的子树内重心并统计答案
有一个问题是当换根时父子关系转置,重儿子要重新更新,并且枚举到的 不能作为重儿子
当找到子树内倍增后的最后一个点后需要判断两个重心的情况,那么将其父亲、重儿子、自己都判断一下统计进答案
杂题
考虑两个人的移动方式是怎样的
一定是一个人往叶子上走,这个人等着传送,直到最后一次出发,这个人同时向剩下的那个子树中移动
那么设 表示到达 且子树外已经计算过的最优情况
转移方程为
为主链上结点 u 到 结点 v (不包括结点 v) 往外伸出去的子树中最深的叶子的深度
观察发现有结论每次从 转移过来的同时,只需要转移 的节点即可
进一步发现这类似于多步转移,那么只需要找到单调递减栈上最后一个点转移即可
于是就需要找到每个点上方第一个 大于它的点
当然可以二分栈之类的科技
一种神奇的方式是从下往上合并,然后子树之间暴力抵消更新,这样做完的复杂度正确!
考虑这个过程的本质,其实就是说从 的深度都 的深度,把所有深度的点的权值取 作为深度的权值,那么一定是对着深度尽量小的权值一直死,然后晋级到下一深度,以此类推,直到能走过路径上的最大值为止
于是相当于要动态地对深度维护单调栈
现在要做的就是单调栈的合并,可以发现启发式合并复杂度正确,为
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效