以点为对象的树形DP寒假集训笔记
课件
一、核心概念
1. 定义
以顶点为决策对象的树形DP,核心是通过节点状态关联子树最优解,状态通常定义为 f ( u , s t a t e ) f(u, state) f(u,state):
- u u u:当前处理的子树根节点
- s t a t e state state:节点状态(如选/不选、覆盖来源、染色等)
- 含义:以 u u u 为根的子树中, u u u 处于该状态时的最优答案(最大权值/最小代价)
2. 核心逻辑
决策核心围绕"当前节点状态与直接子节点状态的依赖关系",通过DFS递归遍历树结构,利用子树DP结果推导当前节点DP值,时间复杂度通常为 O ( n ) O(n) O(n)。
二、经典题型详解
(一)最大独立集
例题
1. 问题描述
树上选若干顶点,相邻顶点不能同时选中,求选中顶点的最大权值和。
2. 状态定义
- d p u 0 dpu0 dpu0:不选节点 u u u 时,以 u u u 为根的子树最大权值
- d p u 1 dpu1 dpu1:选中节点 u u u 时,以 u u u 为根的子树最大权值
3. 转移方程
- 选中 u u u:子节点必须不选
d p u 1 = a u + ∑ v ∈ s o n ( u ) d p v 0 dpu1 = au + \sum_{v \in son(u)} dpv0 dpu1=au+∑v∈son(u)dpv0( a u au au 为节点 u u u 权值) - 不选 u u u:子节点可选可不选,取最大值
d p u 0 = ∑ v ∈ s o n ( u ) m a x ( d p v 0 , d p v 1 ) dpu0 = \sum_{v \in son(u)} max(dpv0, dpv1) dpu0=∑v∈son(u)max(dpv0,dpv1)
( 二 )最小支配集
例题
一、问题描述
每个顶点可以放置守卫,守卫能保护自身及相邻的节点。求最少需要选择多少个顶点放置守卫,使得树上所有节点都被保护(无任何节点遗漏覆盖)。
二、核心分析
-
与最大独立集的区别
最大独立集仅需考虑"节点选或不选"的二元状态,而最小支配集需要明确"节点被谁保护",状态更复杂,需通过多状态区分覆盖来源,避免遗漏保护场景。
-
关键思考
节点的保护来源仅三种可能:自身为守卫、被子节点保护、被父节点保护,因此需设计三状态覆盖所有场景,确保无后效性。
三、状态定义(核心)
定义 d p u s t a t e dpustate dpustate 表示以 u u u 为根的子树中, u u u 处于对应状态时的最少守卫数:
- d p u 0 dpu0 dpu0: u u u 被选中(自身放置守卫),可覆盖 u u u 及其所有子节点;
- d p u 1 dpu1 dpu1: u u u 未被选中,但被子节点保护( u u u 无守卫,至少有一个子节点放置了守卫);
- d p u 2 dpu2 dpu2: u u u 未被选中,依赖父节点保护( u u u 无守卫,需等待父节点的守卫覆盖自身)。
四、状态转移方程
1. d p u 0 dpu0 dpu0 转移( u u u 自身为守卫)
当 u u u 放置守卫时,其所有子节点 v v v 可处于任意状态(无需额外约束,因 u u u 的守卫已覆盖 v v v),只需取每个子节点所有状态的最小值累加,再加上当前节点的守卫数:
dp\[u\]\[0\] = 1 + \\sum_{v \\in children(u)} \\min(dp\[v\]\[0\], dp\[v\]\[1\], dp\[v\]\[2\])
2. d p u 2 dpu2 dpu2 转移( u u u 依赖父节点保护)
当 u u u 未放置守卫且依赖父节点时, u u u 的子节点 v v v 不能依赖 u u u 保护(否则 v v v 会无覆盖),因此 v v v 只能是"自身选中"或"被子节点保护",取两者最小值累加:
dp\[u\]\[2\] = \\sum_{v \\in children(u)} \\min(dp\[v\]\[0\], dp\[v\]\[1\])
3. d p u 1 dpu1 dpu1 转移( u u u 被子节点保护,核心难点)
当 u u u 未放置守卫且需被子节点保护时,必须保证至少有一个子节点 v v v 放置了守卫(即 v v v 处于 d p v 0 dpv0 dpv0 状态) ,否则 u u u 会未被覆盖。转移分两步:
-
Step 1:计算所有子节点取 m i n ( d p v 0 , d p v 1 ) min(dpv0, dpv1) min(dpv0,dpv1) 的总和 S u m Sum Sum,同时记录是否存在子节点满足 d p v 0 ≤ d p v 1 dpv0 ≤ dpv1 dpv0≤dpv1(即该子节点选择 d p v 0 dpv0 dpv0 更优):
Sum = \\sum_{v \\in children(u)} \\min(dp\[v\]\[0\], dp\[v\]\[1\])
-
Step 2:分情况讨论:
- Case A:若存在至少一个子节点 v v v 满足 d p v 0 ≤ d p v 1 dpv0 ≤ dpv1 dpv0≤dpv1,则 S u m Sum Sum 中已包含 d p v 0 dpv0 dpv0, u u u 可被该子节点保护,此时 d p u 1 = S u m dpu1 = Sum dpu1=Sum;
- Case B:若所有子节点均满足 d p v 1 < d p v 0 dpv1 < dpv0 dpv1<dpv0,则 S u m Sum Sum 中全为 d p v 1 dpv1 dpv1(无子女守卫),需强制将一个子节点 v v v 转换为 d p v 0 dpv0 dpv0 状态,取转换成本最小的( d p v 0 − d p v 1 dpv0 - dpv1 dpv0−dpv1)累加至 S u m Sum Sum:
dp\[u\]\[1\] = Sum + \\min_{v \\in children(u)} { dp\[v\]\[0\] - dp\[v\]\[1\] }
( 三 )一题多解
例题
1. 贪心核心逻辑
每个节点必须被保护(要么自身为守卫,要么被相邻节点保护)。对于深度最深的节点,其保护来源仅两种可能:自身或父节点。贪心选择父节点放置守卫,原因是父节点的覆盖范围更广(可同时保护父节点自身、当前节点及当前节点的兄弟节点),能最小化总守卫数。
2. 贪心执行步骤
- 从叶子节点向上遍历树(按深度从大到小排序);
- 若当前节点未被覆盖,在其父节点放置守卫,并标记父节点、当前节点及父节点的所有子节点(当前节点的兄弟节点)为已覆盖;
- 若遍历至根节点且根节点未被覆盖,则在根节点自身放置守卫。
3. 贪心算法的局限性
仅适用于 所有节点代价相同 的场景(如"放置守卫的成本均为1"),无法处理节点带权的情况(如不同节点放置守卫的成本不同,例如保安站岗)。
二、变式:覆盖半径为2的支配集(消防局的设立)
1. 问题描述
与基础最小支配集的核心区别是"守卫覆盖半径为 2 2 2 ":若在节点 C C C 放置守卫,可覆盖C及其距离 ≤ 2 ≤2 ≤2 的所有节点(如边 A − B − C − D − E A-B-C-D-E A−B−C−D−E 中, C C C 的守卫可覆盖 A 、 B 、 C 、 D 、 E A、B、C、D、E A、B、C、D、E 五个节点)。要求找到最少的守卫数,使树上所有节点被覆盖。
2. 核心思考
覆盖半径扩大至 2 2 2 后,节点的覆盖来源更复杂,需考虑"消防局"可能的位置:自身、儿子、孙子、父亲、爷爷。因此需定义多状态,覆盖所有"覆盖来源"和"未覆盖待救援"的场景,确保无遗漏。
3. 五状态定义(核心)
定义 d p u s t a t e dpustate dpustate 表示以 u u u 为根的子树中,对应状态下的最少守卫数,状态需涵盖所有覆盖与未覆盖场景:
- d p u 0 dpu0 dpu0:至少 u u u 及其子树全部被覆盖,且 u u u 处设消防局(覆盖能力最强,可覆盖 u u u 的爷爷、父亲、自身、儿子、孙子);
- d p u 1 dpu1 dpu1:至少 u u u 及其子树全部被覆盖, u u u 被其儿子设的消防局覆盖(儿子的消防局半径2,可覆盖 u u u);
- d p u 2 dpu2 dpu2:至少 u u u 及其子树全部被覆盖, u u u 被其孙子设的消防局覆盖(孙子的消防局半径2,可覆盖 u u u);
- d p u 3 dpu3 dpu3:至少 u u u 的子树内部全部覆盖,但 u u u 未被覆盖(需等待父亲或爷爷的消防局救援,父亲的消防局半径2可覆盖 u u u,爷爷的消防局半径2也可覆盖 u u u);
- d p u 4 dpu4 dpu4:至少 u u u 的孙子辈全部覆盖,但 u u u 和儿子都未被覆盖(需等待父节点的消防局救援,父节点的消防局半径2可覆盖 u u u 和 u u u 的儿子)。
4. 状态转移思路(对所有儿子 v v v 操作)
| 状态 | 含义 | 转移逻辑 |
|---|---|---|
| d p u 0 dpu0 dpu0 | u u u 设消防局,自身及子树全覆盖 | 儿子 v v v 可处于任意状态(因 u u u 的消防局已覆盖 v v v),累加每个儿子所有状态的最小值: ∑ m i n ( d p v 0 , d p v 1 , d p v 2 , d p v 3 , d p v 4 ) \sum min(dpv0, dpv1, dpv2, dpv3, dpv4) ∑min(dpv0,dpv1,dpv2,dpv3,dpv4) |
| d p u 1 dpu1 dpu1 | u u u被子节点消防局覆盖,自身及子树全覆盖 | 至少有一个儿子 v v v 处于 d p v 0 dpv0 dpv0 状态(儿子设消防局,半径2覆盖 u u u),其余儿子可处于 d p v 0 d p v 3 dpv0~dpv3 dpv0 dpv3 状态(确保子树覆盖且不影响 u u u 的覆盖),取满足条件的最小和 |
| d p u 2 dpu2 dpu2 | u u u 被孙子消防局覆盖,自身及子树全覆盖 | 至少有一个儿子 v v v 处于 d p v 1 dpv1 dpv1 状态(孙子设消防局,通过儿子传递覆盖 u u u),其余儿子可处于 d p v 0 d p v 2 dpv0~dpv2 dpv0 dpv2 状态,取满足条件的最小和 |
| d p u 3 dpu3 dpu3 | u u u 子树全覆盖, u u u 未覆盖(等父/爷爷救援) | 所有儿子 v v v 必须已被覆盖(不能依赖 u u u 救援),因此儿子仅能处于 d p v 0 d p v 2 dpv0~dpv2 dpv0 dpv2 状态,累加每个儿子的最小值: ∑ m i n ( d p v 0 , d p v 1 , d p v 2 ) \sum min(dpv0, dpv1, dpv2) ∑min(dpv0,dpv1,dpv2) |
| d p u 4 dpu4 dpu4 | 孙子辈全覆盖, u u u 和儿子未覆盖(等父节点救援) | 所有儿子 v v v 的孙子辈已覆盖,儿子可处于 d p v 0 d p v 3 dpv0~dpv3 dpv0 dpv3 状态(儿子未覆盖可由父节点 u u u 的父节点救援),累加每个儿子的最小值: ∑ m i n ( d p v 0 , d p v 1 , d p v 2 , d p v 3 ) \sum min(dpv0, dpv1, dpv2, dpv3) ∑min(dpv0,dpv1,dpv2,dpv3) |
5. 实现技巧
d p u 1 dpu1 dpu1 和 d p u 2 dpu2 dpu2 的转移需确保"至少一个儿子满足特定状态",可利用 d p u 4 dpu4 dpu4 和 d p u 3 dpu3 dpu3 简化计算:
- d p u 1 dpu1 dpu1:先计算 d p u 4 dpu4 dpu4(儿子取 m i n ( d p v 0 d p v 3 ) min(dpv0~dpv3) min(dpv0 dpv3) 的总和),再扫描所有儿子,找到"替换为 d p v 0 dpv0 dpv0 成本最小"的情况,即:
d p u 1 = m i n ( d p u 1 , d p u 4 − m i n ( d p v 0 d p v 3 ) + d p v 0 ) dpu1 = min(dpu1, dpu4 - min(dpv0~dpv3) + dpv0) dpu1=min(dpu1,dpu4−min(dpv0 dpv3)+dpv0)
- d p u 2 dpu2 dpu2:先计算 d p u 3 dpu3 dpu3(儿子取 m i n ( d p v 0 d p v 2 ) min(dpv0~dpv2) min(dpv0 dpv2) 的总和),再扫描所有儿子,找到"替换为 d p v 1 dpv1 dpv1 成本最小"的情况,即:
d p u 2 = m i n ( d p u 2 , d p u 3 − m i n ( d p v 0 d p v 2 ) + d p v 1 ) dpu2 = min(dpu2, dpu3 - min(dpv0~dpv2) + dpv1) dpu2=min(dpu2,dpu3−min(dpv0 dpv2)+dpv1)