1. 题目解析与算法思路
1.1 理解毒瘤集的定义
毒瘤集是这道题的核心概念。根据题意,毒瘤集需要满足:集合中任意两个节点之间不能存在祖先-后代关系。换句话说,你不能同时选择一个节点和它的任意祖先或后代。
举个例子,假设我们有一棵树:
code复制 1
/ \
2 5
/ \
3 4
那么合法的毒瘤集包括:
- 单元素集合:{1}, {2}, {3}, {4},
- 多元素集合:{2,5}, {3,4}, {3,5}, {4,5},
而像{1,2}这样的集合就不合法,因为1是2的祖先。
1.2 问题转化与动态规划思路
我们需要计算所有合法毒瘤集的元素和之和。直接枚举所有可能的子集显然不可行,因为对于n=1e6的情况,时间复杂度会爆炸。
这里我们需要使用树形动态规划(Tree DP)的技巧。定义两个状态:
- f[u]:以u为根的子树中,所有包含u的毒瘤集的毒瘤指数之和
- g[u]:以u为根的子树中,所有不包含u的毒瘤集的毒瘤指数之和
关键点在于理解状态转移方程。对于每个节点u,我们需要考虑两种情况:
- 选择u节点:那么u的所有子节点都不能被选择(因为它们是u的后代)
- 不选择u节点:那么子节点可以自由选择(但要满足子节点之间的限制)
1.3 状态转移方程详解
让我们更详细地分析状态转移:
-
初始状态:
- 对于叶子节点u:
- f[u] = w[u](只能选择自己)
- g[u] = 1(空集,和为0,但为了方便计算我们初始化为1)
- 对于叶子节点u:
-
状态转移:
- 对于非叶子节点u,我们需要合并所有子节点的信息
- 如果选择u,那么不能选择任何子节点,所以:
f[u] = w[u] * ∏(g[v] for v in children of u) - 如果不选择u,那么子节点可以自由组合:
g[u] = ∏(f[v] + g[v] for v in children of u)
但是题目中的实现看起来更复杂一些,这是因为它在合并子节点时采用了逐步合并的方式,而不是直接相乘。
1.4 代码中的状态转移分析
让我们看看代码中的实际实现:
cpp复制for(
