CF2070D - Educational Codeforces Round 175 (Rated for Div. 2) D. Tree Jumps题解
算法 | 题解 | CodeForces
2025年3月26日
思路详解
问题分析
我们需要计算树中从根节点出发能够到达的所有节点的路径总数. 这里的路径指的是从根节点向下延伸的简单路径 (不包含回头路).
错误思路分析
最初的朴素想法是从底层向上逐层计算每个节点对上层节点的贡献. 具体来说:
- 每个节点自身贡献1条路径 (只包含自己)
- 每个节点会向其所有非父节点的上层节点贡献其路径数 但这种做法需要对每个节点向上遍历所有祖先节点, 时间复杂度为 , 对于的数据规模显然不可行
正确思路优化
采用动态规划的思想, 结合树的分层处理:
-
分层处理:首先将树按层次划分, 根节点在第0层, 其子节点在第1层, 以此类推.
-
路径计数规则:
- 每个节点至少有1条路径 (只包含自己)
- 每个节点会向其上层节点贡献其路径数 (因为上层节点可以通过子节点到达这些路径)
- 但父节点不能通过子节点到达子节点本身 (即不能直接使用子节点的路径数)
-
关键优化:
- 从底层向上处理, 维护一个”上一层总和”变量
- 对于当前层的每个节点:
- 首先加上上一层所有节点的路径总和 (因为这些节点都可以被当前节点的父节点到达)
- 然后从父节点的计数中减去当前节点的计数 (消除父节点不能到达当前节点的部分)
-
状态转移方程:
- 设表示以为根的子树中从根到的路径数
- (是的下层节点, 是的子节点)
复杂度分析
- 时间复杂度:
- 分层处理需要 时间
- 自底向上处理每层节点也是
- 空间复杂度:
- 需要存储树的结构和每层节点信息
完整代码
for _ in range(int(input())):
# 数据输入
n = int(input())
a = map(int, input().split())
# 下标1对应根节点. 下标0对应0节点, 但是并无0节点, 于是占位.
# 三个数值分别对应 [父节点, 层数, 路径计数].
# 显然的, 每一个节点至少有一条路径, 即为本身.
info = [[0, 0, 0], [0, 0, 1]]
level = [[1]] # 第0层有且仅有根节点.
for i, parent in enumerate(a):
lev = info[parent][1] + 1 # 当前层数为父节点层数加1.
info.append([parent, lev, 1]) # 更新节点信息.
if len(level) <= lev: # 如果当前层数不足则添加一层. (显而易见, 层数只能一次增加1)
level.append([])
level[lev].append(i + 2) # 在目标层加入点, i从0开始, 但是点id从1开始, 并且不包括根节点, 所以点id应该为i+2.
last_lsum = 0 # 上一层的总和
for lst in level[::-1]: # 从最后一层开始遍历.
level_sum = 0 # 当前层总和
for i in lst:
info[i][2] += last_lsum # 将当前点添加上一层的总和.
level_sum += info[i][2] # 将当前点的计数添加到当前层总和
if info[i][0] != 1: # 特判父节点为根节点.
info[info[i][0]][2] -= info[i][2] # 将父节点减少当前节点计数(因为父节点无法到达子节点).
last_lsum = level_sum # 更新上一层的总和.
print(info[1][2] % 998244353) # 输出.
# 提交记录: https://codeforces.com/contest/2070/submission/312559631
# 语言: pypy3.10
# Code by @hsn8086