1 条题解
-
0
问题分析
本题是一道树上路径计数与动态规划结合的综合题,核心是统计满足特定条件的树上路径数量,需融合LCA(最近公共祖先)、DFS序、线段树和动态规划等技术,处理树结构与额外边的复杂交互。
算法思路
1. 树的预处理
- 通过DFS遍历,计算每个节点的入时间戳
in[u]
和出时间戳out[u]
,构建树的DFS序,用于后续子树范围判定。 - 用二进制提升法预处理LCA结构,使任意两点的LCA查询时间复杂度降至。
- 同步计算节点深度
dep[u]
和祖先数组fa[u][i]
,其中fa[u][i]
表示节点u
向上层的祖先。
2. 边的分类与处理
将所有边(共
m
条)分为两类,分别处理其对结果的贡献:- 树内边:两点在同一子树内(满足
in[eu[i]] <= in[ev[i]] && out[ev[i]] <= out[eu[i]]
)。- 更新
siz1
和siz2
数组,记录子树内边的数量与额外贡献。 - 通过
get_anc
函数找到特定祖先节点,调整相关统计值。
- 更新
- 横跨边:连接不同子树的边。
- 先通过LCA找到两点的最近公共祖先
p
。 - 在
oper[p]
中记录区间更新操作,后续通过线段树批量处理。
- 先通过LCA找到两点的最近公共祖先
3. 线段树的核心作用
线段树用于高效维护区间乘积信息,支持三种核心操作:
- 区间乘法更新:对指定区间
[L, R]
的所有元素乘以某个值d
。 - 单点更新:直接修改某个位置的元素值。
- 区间查询:计算指定区间
[L, R]
的元素和,用于快速获取子树范围内的路径贡献。
线段树的每个节点存储两个值:
s[id]
(区间和)和laz[id]
(乘法懒标记),通过懒标记机制减少重复更新,确保每次操作的时间复杂度为。4. 动态规划的状态设计与转移
在
dfs2
中定义四个状态,通过子树信息递推计算路径数量:- :不包含当前节点的合法路径数。
- :包含当前节点的单条合法路径数。
- :包含当前节点的两条合法路径数。
- :包含当前节点且满足题目特定条件的路径数(与输入节点
ipt
相关)。
状态转移核心逻辑:
- 初始化当前节点的四个状态值。
- 遍历每个子节点,结合线段树查询的子树贡献
f
,更新四个状态:- (继承不包含当前节点的路径,乘以子树的路径基数)。
- $g1 = (f1 \times pw[siz1[v]] + f0 \times f) \mod MOD$(原有单路径继承 + 新增以当前节点为起点的路径)。
- $g2 = (f2 \times pw[siz1[v]] + f1 \times f) \mod MOD$(原有双路径继承 + 单路径与新路径组合)。
- $g3 = ((f2 + f3) \times f + f3 \times pw[siz1[v]]) \mod MOD$(复杂路径组合,包含特定条件路径)。
- 将更新后的状态
g0~g3
赋值回f0~f3
,继续处理下一个子节点。
5. 结果计算与调整
- 遍历过程中,结合
oper[u]
中记录的操作,通过线段树调整区间贡献,并累加符合条件的路径数到ans
。 - 最终对
ans
进行修正:ans = (-ans % MOD + MOD) % MOD
,确保结果在范围内()。
关键公式与复杂度
-
快速幂公式:计算,用于高效获取幂次结果。
ll ksm(ll a, int b) { ll r = 1; while (b) { if (b & 1) r = r * a % MOD; a = a * a % MOD; b >>= 1; } return r; }
时间复杂度:。
-
逆元计算:题目中
inv2 = (MOD + 1) / 2
,利用费马小定理,,用于偶数相关的除法取模。 -
整体复杂度:
- 预处理阶段:(DFS序 + LCA预处理)。
- 边处理阶段:(每条边的LCA查询 + 操作记录)。
- 动态规划阶段:(每个节点的线段树操作)。
- 总时间复杂度:,可处理的规模。
代码核心模块总结
模块 功能描述 LCA预处理 二进制提升构建祖先数组,支持查询 线段树 维护区间乘积与和,支持懒标记优化的区间更新/查询、单点更新 边分类处理 区分树内边与横跨边,分别更新统计数组或记录操作 动态规划(dfs2) 通过状态转移,结合线段树查询,累加最终答案 - 通过DFS遍历,计算每个节点的入时间戳
- 1
信息
- ID
- 3105
- 时间
- 3000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者