情景树上根到叶路径中最小的最大回撤
Minimum Max-Drawdown Across Root-to-Leaf Paths
开始编码输入是一棵 N 节点的情景树,由两个平行数组给出。node_returns[i] 是节点 i 处实现的当期可加 return(带符号 float,取值 [-1.0, 1.0])。children[i] 是节点 i 的子节点 index 列表;节点 0 是根,每个非根 index 在所有 children 列表中恰好出现一次,故输入是一棵合法的有根树(无环、连通)。实现 solution(node_returns: list[float], children: list[list[int]]) -> float,返回所有根到叶路径中,路径 max-drawdown 的最小值。
对单条根到叶路径 P = (v_0, v_1, ..., v_L)(其中 v_0 = 0),定义 cumret_k = sum(node_returns[v_j] for j <= k),peak_k = max(cumret_0, ..., cumret_k)。位置 k 处的 drawdown 为 dd_k = peak_k - cumret_k,由构造 dd_k >= 0。该路径的 max_dd_path = max(dd_0, ..., dd_L)。函数必须返回 min(max_dd_path for 每条根到叶路径)。
算法:从根做 DFS,把 (cumret, peak, max_dd) 作为状态传入每个递归调用。在根处:cumret = node_returns[0],peak = cumret,max_dd = 0。对当前节点的每个子节点 c:更新 cumret += node_returns[c],peak = max(peak, cumret),dd_c = peak - cumret,max_dd = max(max_dd, dd_c),再递归。到达叶节点时,该路径的 max_dd 用于更新全局最小值。所有路径都探索完后返回该最小值。
最关键的细节有两条。第一是每次递归调用的状态独立拷贝:每个子节点必须收到属于自己的 (cumret, peak, max_dd) 副本,而不是共享的可变容器。把子状态以不可变元组形式压栈或按值传参即可保证兄弟分支互不干扰;若改成在下行过程中修改一个共享 dict/list,兄弟分支 A 就会污染兄弟分支 B 的起始状态。第二是根节点处 running peak 的初始化:peak = node_returns[0](不是 -inf、不是 0),这样恰好保证 dd_root = 0,与"回撤非负"的定义对齐。
例
solution(
node_returns = [0.25, 0.50, -0.125, -0.125, -0.500, 0.500],
children = [[1, 2], [3, 4], [5], [], [], []]
)返回 0.125。树以节点 0 为根,子节点为 1、2;节点 1 的子节点是 3、4;节点 2 的子节点是 5。共三条根到叶路径:
0 -> 1 -> 3:cumret0.25, 0.75, 0.625;peak0.25, 0.75, 0.75;dd0, 0, 0.125;max_dd = 0.125。0 -> 1 -> 4:cumret0.25, 0.75, 0.25;peak0.25, 0.75, 0.75;dd0, 0, 0.500;max_dd = 0.500。0 -> 2 -> 5:cumret0.25, 0.125, 0.625;peak0.25, 0.25, 0.625;dd0, 0.125, 0;max_dd = 0.125。
两条路径并列 0.125,另一条 0.500;最小值为 0.125。注意:终端 cumret 最大的路径(0->1->3,终端 0.75)并不是 max-drawdown 最小的路径——本题问的是路径内部的峰谷差,与终端值无关。
实践背景
风险台搭建一棵多期情景树,每个节点代表"下一期可能实现的某个 return"分支,节点的孩子代表在已经走过当前节点之后下一期的不同情景。对于每个候选对冲策略,风险台想知道:在树上所有根到叶路径中,最小的那条路径 max-drawdown 是多少?这条 min-max-drawdown 是一种"路径下界"风险度量——若该值很小,说明至少存在一条情景路径,这条路径上的策略不会出现剧烈的"峰到谷"回撤;换言之,在合适的未来情景实现下,策略走得比较平稳。它在操作意义上明显不同于"无中间回撤的最深叶节点"(一个深度 + 二元路径可行性切片)、"概率加权终端 PnL 期望"(不关心路径内峰谷结构)、以及"最差叶节点 PnL"(只看终端 cumret,根本不关心 running peak)。本题这种"DFS 携带状态向下"的模式同样出现在压力测试路径行走、情景化 VaR 下界、以及策略组合的路径存活性检验中。
约束条件
- 1 <= N <= 800,其中 N == len(node_returns) == len(children);根为 index 0,每个非根 index 恰好属于一个父亲的 children 列表(合法的有根树,无环、连通)。
- children[u] 是 u 的子节点 index 列表;子 index 取值在 [1, N) 区间,且每个非零 index 在所有 children 列表中恰好出现一次。children[u] 可以为空(此时 u 是叶节点)。
- 每个 node_returns[i] 为带符号 float,取值在 [-1.0, 1.0](节点 i 处实现的当期可加 return)。
- 函数名为 `solution`。返回 Python float —— 所有根到叶路径中,路径 max-drawdown 的最小值(非负)。比对器为 approx:rel_tol = 1e-9,abs_tol = 1e-9。
- 当 N == 1(根本身就是唯一的叶节点)时,唯一路径只含根,peak == cumret,drawdown 为 0;函数必须返回 0.0。
样例
Case 1 · statement-example: tie at 0.125 between two non-trivial paths
输入: [[0.25,0.5,-0.125,-0.125,-0.5,0.5],[[1,2],[3,4],[5],[],[],[]]]
期望: 0.125
路径 0->1->3、0->1->4、0->2->5 的 max-drawdown 分别为 0.125、0.500、0.125;min 为 0.125。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: tie at 0.125 between two non-trivial paths
输入: [[0.25,0.5,-0.125,-0.125,-0.5,0.5],[[1,2],[3,4],[5],[],[],[]]]
期望: 0.125
路径 0->1->3、0->1->4、0->2->5 的 max-drawdown 分别为 0.125、0.500、0.125;min 为 0.125。