通用二叉树上的逆向归纳
Backward Induction on a Generic Binary Tree
开始编码一棵情景树沿时间向前展开:每个内部节点上世界先实现一个状态,立刻支付 value_at_node,然后以概率 prob_up 进入 up_child、以概率 1 − prob_up 进入 down_child;未来子树产生的现金流要再乘一个每步折扣因子 discount。叶子节点上世界停下,实现的现金流就是 value。把这棵树折叠成一个标量——「以现在为时点的期望值」——的标准做法叫逆向归纳(backward induction):从叶子算起、一层层往根回卷。
请实现 solution(tree: dict, discount: float) -> float,返回 tree 在每步折扣因子 discount 下的根节点期望值。tree 用嵌套字典编码:
- 叶子:
{"is_leaf": True, "value": <float>} - 内部节点:
{"is_leaf": False, "value_at_node": <float>, "prob_up": <float>, "up_child": <subtree>, "down_child": <subtree>}
递推关系是:
EV(leaf) = leaf["value"]
EV(internal) = value_at_node + discount · (prob_up · EV(up_child) + (1 − prob_up) · EV(down_child))校验:使用前请检查 discount ∈ [0, 1] 与每个 prob_up ∈ [0, 1],超界请抛 ValueError 并附清晰错误信息。discount = 0(短视)、discount = 1(不衰减)都是合法的;prob_up = 0 与 prob_up = 1(确定性分支)也合法。
示例
solution({"is_leaf": True, "value": 42.5}, 0.95) 返回 42.5。根本身就是叶子,折扣因子根本用不上——没有未来子树要折现。
solution({"is_leaf": False, "value_at_node": 0.0, "prob_up": 0.6, "up_child": {"is_leaf": True, "value": 100.0}, "down_child": {"is_leaf": True, "value": -40.0}}, 0.95) 返回 41.8。深度 1 的树,两个孩子都是叶子:0 + 0.95 · (0.6 · 100 + 0.4 · (−40)) = 0.95 · 44 = 41.8。
solution({"is_leaf": False, "value_at_node": 5.0, "prob_up": 0.7, "up_child": {"is_leaf": True, "value": 50.0}, "down_child": {"is_leaf": False, "value_at_node": 1.0, "prob_up": 0.3, "up_child": {"is_leaf": True, "value": 10.0}, "down_child": {"is_leaf": True, "value": -20.0}}}, 0.9) 大约返回 34.097。深度-2 的非对称树:上行子树是叶子,下行子树是另一个内部节点带自己的两片叶子——这种形状重组型格点表达不了。
实战背景
通用递归二叉树上的逆向归纳,远不止用在衍生品定价上——只要你手上有一棵带阶段支付与概率转移的离散决策树或情景树,都用得上:研究侧的情景分析(「若事件 A 发生,则事件 B 有 30% 概率发生,那时候……」)、策略博弈树的局面值评估、显式分支状态空间上的动态规划、结构化不确定性下的期望效用计算。本题的树形是任意的(而非重组型格点),所以正是当一个节点的两个子节点拥有不同后续可能性时——也就是格点捷径失效时——你应当抓起来用的工具。把这个原语写好之后,换掉叶子基底情况(改成返回 payoff)、换掉合并算子(把折扣期望值换成 max 得到最优策略树,换成风险加权和得到最坏情景聚合),就分别得到决策树 EV、博弈树 minimax、风险调整情景汇总——同一个递归骨架可以撑起这几类不同任务。
约束条件
- 每个节点都是字典。叶子:`{"is_leaf": true, "value": <float>}`,仅这两个键。内部节点:`{"is_leaf": false, "value_at_node": <float>, "prob_up": <float>, "up_child": <subtree>, "down_child": <subtree>}`。
- `prob_up ∈ [0, 1]`,每个内部节点都要满足;若超界请抛 `ValueError`。边界值 `0.0` 和 `1.0` 是合法输入(确定性分支)。
- `discount ∈ [0, 1]`;若超界请抛 `ValueError`。边界值 `0.0`(短视——忽略所有未来现金流)和 `1.0`(不衰减)都是合法的。
- `value`、`value_at_node` 为任意有限实数(可正可负)。
- 树深度 ≤ 30。形状任意——任一内部节点的两侧子树形状彼此独立,因此这棵树**不是**重组型格点。
- 返回值浮点容差:rel_tol = 1e-9,abs_tol = 1e-12。
样例
Case 1 · leaf-only root: degenerate base case
输入: [{"is_leaf":true,"value":42.5},0.95]
期望: 42.5
退化情形:根节点本身就是叶子。直接返回 value=42.5;折扣因子在这条递归路径上根本没被使用,因为没有 future 子树要折现。这道题用来确认你的实现先判 leaf 再读 prob_up/up_child/down_child(leaf 上根本没有这些键)。
Case 2 · depth-1 with both children leaves
输入: [{"is_leaf":false,"value_at_node":0,"prob_up":0.6,"up_child":{"is_leaf":true,"value":100},"down_child":{"is_leaf":true,"value":-40}},0.95]
期望: 41.8
深度 1 的最简单内部节点:root.value_at_node = 0,prob_up = 0.6,两个叶子各为 100 和 −40,折扣 0.95。手算:0 + 0.95 · (0.6 · 100 + 0.4 · (−40)) = 0.95 · (60 − 16) = 0.95 · 44 = 41.8。这是 EV 递推一步的标准形态。
Case 3 · depth-2 asymmetric: up=leaf, down=internal
输入: [{"is_leaf":false,"value_at_node":5,"prob_up":0.7,"up_child":{"is_leaf":true,"value":50},"down_child":{"is_leaf":false,"value_at_node":1,"prob_up":0.3,"up_child":{"is_leaf":true,"value":10},"down_child":{"is_leaf":true,"value":-20}}},0.9]
期望: 34.096999999999994
树形状不对称:up 是叶子,down 是另一个深度-1 子树。这是与重组型二叉格(recombining binomial lattice)最直观的区别——这里两侧的子树不必同形,这正是 generic-tree-recursion 与 derivatives/tree-pricing 的分界点。down_child 的 EV:1 + 0.9·(0.3·10 + 0.7·(−20)) = 1 + 0.9·(−11) = −8.9;root:5 + 0.9·(0.7·50 + 0.3·(−8.9)) = 5 + 0.9·(35 − 2.67) = 5 + 0.9·32.33 = 5 + 29.097 ≈ 34.097。
Case 4 · prob_up=1.0 boundary: all weight on up branch
输入: [{"is_leaf":false,"value_at_node":2,"prob_up":1,"up_child":{"is_leaf":true,"value":10},"down_child":{"is_leaf":true,"value":-1000}},0.5]
期望: 7
概率边界 prob_up=1.0:down 分支贡献被 (1−p)=0 抹掉,down_child=−1000 完全不影响结果。手算:2 + 0.5·(1·10 + 0·(−1000)) = 2 + 5 = 7。这道题验证你写的是 p·EV_up + (1−p)·EV_down 而不是反过来。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · leaf-only root: degenerate base case
输入: [{"is_leaf":true,"value":42.5},0.95]
期望: 42.5
退化情形:根节点本身就是叶子。直接返回 value=42.5;折扣因子在这条递归路径上根本没被使用,因为没有 future 子树要折现。这道题用来确认你的实现先判 leaf 再读 prob_up/up_child/down_child(leaf 上根本没有这些键)。
Case 2 · depth-1 with both children leaves
输入: [{"is_leaf":false,"value_at_node":0,"prob_up":0.6,"up_child":{"is_leaf":true,"value":100},"down_child":{"is_leaf":true,"value":-40}},0.95]
期望: 41.8
深度 1 的最简单内部节点:root.value_at_node = 0,prob_up = 0.6,两个叶子各为 100 和 −40,折扣 0.95。手算:0 + 0.95 · (0.6 · 100 + 0.4 · (−40)) = 0.95 · (60 − 16) = 0.95 · 44 = 41.8。这是 EV 递推一步的标准形态。
Case 3 · depth-2 asymmetric: up=leaf, down=internal
输入: [{"is_leaf":false,"value_at_node":5,"prob_up":0.7,"up_child":{"is_leaf":true,"value":50},"down_child":{"is_leaf":false,"value_at_node":1,"prob_up":0.3,"up_child":{"is_leaf":true,"value":10},"down_child":{"is_leaf":true,"value":-20}}},0.9]
期望: 34.096999999999994
树形状不对称:up 是叶子,down 是另一个深度-1 子树。这是与重组型二叉格(recombining binomial lattice)最直观的区别——这里两侧的子树不必同形,这正是 generic-tree-recursion 与 derivatives/tree-pricing 的分界点。down_child 的 EV:1 + 0.9·(0.3·10 + 0.7·(−20)) = 1 + 0.9·(−11) = −8.9;root:5 + 0.9·(0.7·50 + 0.3·(−8.9)) = 5 + 0.9·(35 − 2.67) = 5 + 0.9·32.33 = 5 + 29.097 ≈ 34.097。
Case 4 · prob_up=1.0 boundary: all weight on up branch
输入: [{"is_leaf":false,"value_at_node":2,"prob_up":1,"up_child":{"is_leaf":true,"value":10},"down_child":{"is_leaf":true,"value":-1000}},0.5]
期望: 7
概率边界 prob_up=1.0:down 分支贡献被 (1−p)=0 抹掉,down_child=−1000 完全不影响结果。手算:2 + 0.5·(1·10 + 0·(−1000)) = 2 + 5 = 7。这道题验证你写的是 p·EV_up + (1−p)·EV_down 而不是反过来。