寒假集训笔记·以点为对象的树形DP

以点为对象的树形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 ] dp[u][0] dp[u][0]:不选节点 u u u 时,以 u u u 为根的子树最大权值
  • d p [ u ] [ 1 ] dp[u][1] dp[u][1]:选中节点 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 ] dp[u][1] = a[u] + \sum_{v \in son(u)} dp[v][0] dp[u][1]=a[u]+∑v∈son(u)dp[v][0]( a [ u ] a[u] a[u] 为节点 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 ] ) dp[u][0] = \sum_{v \in son(u)} max(dp[v][0], dp[v][1]) dp[u][0]=∑v∈son(u)max(dp[v][0],dp[v][1])

( 二 )最小支配集

例题

一、问题描述

每个顶点可以放置守卫,守卫能保护自身及相邻的节点。求最少需要选择多少个顶点放置守卫,使得树上所有节点都被保护(无任何节点遗漏覆盖)。

二、核心分析
  1. 与最大独立集的区别

    最大独立集仅需考虑"节点选或不选"的二元状态,而最小支配集需要明确"节点被谁保护",状态更复杂,需通过多状态区分覆盖来源,避免遗漏保护场景。

  2. 关键思考

    节点的保护来源仅三种可能:自身为守卫、被子节点保护、被父节点保护,因此需设计三状态覆盖所有场景,确保无后效性。

三、状态定义(核心)

定义 d p [ u ] [ s t a t e ] dp[u][state] dp[u][state] 表示以 u u u 为根的子树中, u u u 处于对应状态时的最少守卫数:

  • d p [ u ] [ 0 ] dp[u][0] dp[u][0]: u u u 被选中(自身放置守卫),可覆盖 u u u 及其所有子节点;
  • d p [ u ] [ 1 ] dp[u][1] dp[u][1]: u u u 未被选中,但被子节点保护( u u u 无守卫,至少有一个子节点放置了守卫);
  • d p [ u ] [ 2 ] dp[u][2] dp[u][2]: u u u 未被选中,依赖父节点保护( u u u 无守卫,需等待父节点的守卫覆盖自身)。
四、状态转移方程

1. d p [ u ] [ 0 ] dp[u][0] dp[u][0] 转移( 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 ] dp[u][2] dp[u][2] 转移( 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 ] dp[u][1] dp[u][1] 转移( u u u 被子节点保护,核心难点)

当 u u u 未放置守卫且需被子节点保护时,必须保证至少有一个子节点 v v v 放置了守卫(即 v v v 处于 d p [ v ] [ 0 ] dp[v][0] dp[v][0] 状态) ,否则 u u u 会未被覆盖。转移分两步:

  • Step 1:计算所有子节点取 m i n ( d p [ v ] [ 0 ] , d p [ v ] [ 1 ] ) min(dp[v][0], dp[v][1]) min(dp[v][0],dp[v][1]) 的总和 S u m Sum Sum,同时记录是否存在子节点满足 d p [ v ] [ 0 ] ≤ d p [ v ] [ 1 ] dp[v][0] ≤ dp[v][1] dp[v][0]≤dp[v][1](即该子节点选择 d p [ v ] [ 0 ] dp[v][0] dp[v][0] 更优):

    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 ] dp[v][0] ≤ dp[v][1] dp[v][0]≤dp[v][1],则 S u m Sum Sum 中已包含 d p [ v ] [ 0 ] dp[v][0] dp[v][0], u u u 可被该子节点保护,此时 d p [ u ] [ 1 ] = S u m dp[u][1] = Sum dp[u][1]=Sum;
    • Case B:若所有子节点均满足 d p [ v ] [ 1 ] < d p [ v ] [ 0 ] dp[v][1] < dp[v][0] dp[v][1]<dp[v][0],则 S u m Sum Sum 中全为 d p [ v ] [ 1 ] dp[v][1] dp[v][1](无子女守卫),需强制将一个子节点 v v v 转换为 d p [ v ] [ 0 ] dp[v][0] dp[v][0] 状态,取转换成本最小的( d p [ v ] [ 0 ] − d p [ v ] [ 1 ] dp[v][0] - dp[v][1] dp[v][0]−dp[v][1])累加至 S u m Sum Sum:

    dp\[u\]\[1\] = Sum + \\min_{v \\in children(u)} { dp\[v\]\[0\] - dp\[v\]\[1\] }

( 三 )一题多解

例题

1. 贪心核心逻辑

每个节点必须被保护(要么自身为守卫,要么被相邻节点保护)。对于深度最深的节点,其保护来源仅两种可能:自身或父节点。贪心选择父节点放置守卫,原因是父节点的覆盖范围更广(可同时保护父节点自身、当前节点及当前节点的兄弟节点),能最小化总守卫数。

2. 贪心执行步骤
  1. 从叶子节点向上遍历树(按深度从大到小排序);
  2. 若当前节点未被覆盖,在其父节点放置守卫,并标记父节点、当前节点及父节点的所有子节点(当前节点的兄弟节点)为已覆盖;
  3. 若遍历至根节点且根节点未被覆盖,则在根节点自身放置守卫。
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 ] dp[u][state] dp[u][state] 表示以 u u u 为根的子树中,对应状态下的最少守卫数,状态需涵盖所有覆盖与未覆盖场景:

  • d p [ u ] [ 0 ] dp[u][0] dp[u][0]:至少 u u u 及其子树全部被覆盖,且 u u u 处设消防局(覆盖能力最强,可覆盖 u u u 的爷爷、父亲、自身、儿子、孙子);
  • d p [ u ] [ 1 ] dp[u][1] dp[u][1]:至少 u u u 及其子树全部被覆盖, u u u 被其儿子设的消防局覆盖(儿子的消防局半径2,可覆盖 u u u);
  • d p [ u ] [ 2 ] dp[u][2] dp[u][2]:至少 u u u 及其子树全部被覆盖, u u u 被其孙子设的消防局覆盖(孙子的消防局半径2,可覆盖 u u u);
  • d p [ u ] [ 3 ] dp[u][3] dp[u][3]:至少 u u u 的子树内部全部覆盖,但 u u u 未被覆盖(需等待父亲或爷爷的消防局救援,父亲的消防局半径2可覆盖 u u u,爷爷的消防局半径2也可覆盖 u u u);
  • d p [ u ] [ 4 ] dp[u][4] dp[u][4]:至少 u u u 的孙子辈全部覆盖,但 u u u 和儿子都未被覆盖(需等待父节点的消防局救援,父节点的消防局半径2可覆盖 u u u 和 u u u 的儿子)。

4. 状态转移思路(对所有儿子 v v v 操作)

状态 含义 转移逻辑
d p [ u ] [ 0 ] dp[u][0] dp[u][0] 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(dp[v][0], dp[v][1], dp[v][2], dp[v][3], dp[v][4]) ∑min(dp[v][0],dp[v][1],dp[v][2],dp[v][3],dp[v][4])
d p [ u ] [ 1 ] dp[u][1] dp[u][1] u u u被子节点消防局覆盖,自身及子树全覆盖 至少有一个儿子 v v v 处于 d p [ v ] [ 0 ] dp[v][0] dp[v][0] 状态(儿子设消防局,半径2覆盖 u u u),其余儿子可处于 d p [ v ] [ 0 ] d p [ v ] [ 3 ] dp[v][0]~dp[v][3] dp[v][0] dp[v][3] 状态(确保子树覆盖且不影响 u u u 的覆盖),取满足条件的最小和
d p [ u ] [ 2 ] dp[u][2] dp[u][2] u u u 被孙子消防局覆盖,自身及子树全覆盖 至少有一个儿子 v v v 处于 d p [ v ] [ 1 ] dp[v][1] dp[v][1] 状态(孙子设消防局,通过儿子传递覆盖 u u u),其余儿子可处于 d p [ v ] [ 0 ] d p [ v ] [ 2 ] dp[v][0]~dp[v][2] dp[v][0] dp[v][2] 状态,取满足条件的最小和
d p [ u ] [ 3 ] dp[u][3] dp[u][3] u u u 子树全覆盖, u u u 未覆盖(等父/爷爷救援) 所有儿子 v v v 必须已被覆盖(不能依赖 u u u 救援),因此儿子仅能处于 d p [ v ] [ 0 ] d p [ v ] [ 2 ] dp[v][0]~dp[v][2] dp[v][0] dp[v][2] 状态,累加每个儿子的最小值: ∑ m i n ( d p [ v ] [ 0 ] , d p [ v ] [ 1 ] , d p [ v ] [ 2 ] ) \sum min(dp[v][0], dp[v][1], dp[v][2]) ∑min(dp[v][0],dp[v][1],dp[v][2])
d p [ u ] [ 4 ] dp[u][4] dp[u][4] 孙子辈全覆盖, u u u 和儿子未覆盖(等父节点救援) 所有儿子 v v v 的孙子辈已覆盖,儿子可处于 d p [ v ] [ 0 ] d p [ v ] [ 3 ] dp[v][0]~dp[v][3] dp[v][0] dp[v][3] 状态(儿子未覆盖可由父节点 u u u 的父节点救援),累加每个儿子的最小值: ∑ m i n ( d p [ v ] [ 0 ] , d p [ v ] [ 1 ] , d p [ v ] [ 2 ] , d p [ v ] [ 3 ] ) \sum min(dp[v][0], dp[v][1], dp[v][2], dp[v][3]) ∑min(dp[v][0],dp[v][1],dp[v][2],dp[v][3])

5. 实现技巧

d p [ u ] [ 1 ] dp[u][1] dp[u][1] 和 d p [ u ] [ 2 ] dp[u][2] dp[u][2] 的转移需确保"至少一个儿子满足特定状态",可利用 d p [ u ] [ 4 ] dp[u][4] dp[u][4] 和 d p [ u ] [ 3 ] dp[u][3] dp[u][3] 简化计算:

  • d p [ u ] [ 1 ] dp[u][1] dp[u][1]:先计算 d p [ u ] [ 4 ] dp[u][4] dp[u][4](儿子取 m i n ( d p [ v ] [ 0 ] d p [ v ] [ 3 ] ) min(dp[v][0]~dp[v][3]) min(dp[v][0] dp[v][3]) 的总和),再扫描所有儿子,找到"替换为 d p [ v ] [ 0 ] dp[v][0] dp[v][0] 成本最小"的情况,即:

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 ] ) dp[u][1] = min(dp[u][1], dp[u][4] - min(dp[v][0]~dp[v][3]) + dp[v][0]) dp[u][1]=min(dp[u][1],dp[u][4]−min(dp[v][0] dp[v][3])+dp[v][0])

  • d p [ u ] [ 2 ] dp[u][2] dp[u][2]:先计算 d p [ u ] [ 3 ] dp[u][3] dp[u][3](儿子取 m i n ( d p [ v ] [ 0 ] d p [ v ] [ 2 ] ) min(dp[v][0]~dp[v][2]) min(dp[v][0] dp[v][2]) 的总和),再扫描所有儿子,找到"替换为 d p [ v ] [ 1 ] dp[v][1] dp[v][1] 成本最小"的情况,即:

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 ] ) dp[u][2] = min(dp[u][2], dp[u][3] - min(dp[v][0]~dp[v][2]) + dp[v][1]) dp[u][2]=min(dp[u][2],dp[u][3]−min(dp[v][0] dp[v][2])+dp[v][1])

相关推荐
历程里程碑16 小时前
普通数组----合并区间
java·数据结构·python·算法·leetcode·职场和发展·tornado
艾莉丝努力练剑16 小时前
hixl vs NCCL:昇腾生态通信库的独特优势分析
运维·c++·人工智能·cann
执风挽^16 小时前
Python基础编程题2
开发语言·python·算法·visual studio code
我在人间贩卖青春16 小时前
C++之new和delete
c++·delete·new
Z9fish17 小时前
sse哈工大C语言编程练习20
c语言·开发语言·算法
Trouvaille ~17 小时前
TCP Socket编程实战(三):线程池优化与TCP编程最佳实践
linux·运维·服务器·网络·c++·网络协议·tcp/ip
晓131317 小时前
第六章 【C语言篇:结构体&位运算】 结构体、位运算全面解析
c语言·算法
iAkuya17 小时前
(leetcode)力扣100 61分割回文串(回溯,动归)
算法·leetcode·职场和发展
June`17 小时前
高并发网络框架:Reactor模式深度解析
linux·服务器·c++