Files
SolutionEuler/solutions/0067.MaxPathSum2/tropical_semiring.md
SidneyZhang a1f849985e 解决问题19
Signed-off-by: SidneyZhang <zly@lyzhang.me>
2025-12-21 16:11:46 +08:00

7.7 KiB
Raw Permalink Blame History

对于数字三角形路径问题,这正是max-plus代数的经典应用场景。使用自顶向下自底向上两种热带代数视角来求解。

import numpy as np

# 定义max-plus半环的负无穷加法单位元
NEG_INF = -np.inf

def max_plus_triangle_optimal(triangle):
    """
    使用max-plus代数求解数字三角形最大路径和
    
    热带代数视角:
    - 状态转移dp[i,j] = triangle[i][j] ⊙ (dp[i-1,j-1] ⊕ dp[i-1,j])
              = triangle[i][j] + max(dp[i-1,j-1], dp[i-1,j])
    - 其中⊕对应max⊙对应+
    """
    rows = len(triangle)
    
    # 创建dp矩阵存储从顶部到每个节点的最优热带积
    dp = [[NEG_INF] * len(row) for row in triangle]
    dp[0][0] = triangle[0][0]  # 起点初始化
    
    # 热带代数前向传播(自顶向下)
    for i in range(1, rows):
        for j in range(len(triangle[i])):
            # 热带加法:取前一行两个父节点的最大值
            candidates = []
            if j < len(triangle[i-1]):  # 左上父节点
                candidates.append(dp[i-1][j])
            if j > 0:  # 右上父节点
                candidates.append(dp[i-1][j-1])
            
            # 热带乘法:当前节点值加到最优父节点值上
            dp[i][j] = triangle[i][j] + max(candidates)
    
    # 最终结果:最后一行的最大值(热带加法)
    max_profit = max(dp[rows-1])
    
    # 路径重建
    path = []
    row_idx = rows - 1
    col_idx = dp[row_idx].index(max_profit)
    path.append(col_idx)
    
    current_profit = max_profit
    for i in range(rows-1, 0, -1):
        # 逆推父节点
        if col_idx == 0:
            # 只能从左上
            col_idx = 0
        elif col_idx == len(triangle[i]) - 1:
            # 只能从右上
            col_idx -= 1
        else:
            # 选择热带积更大的父节点
            left = dp[i-1][col_idx-1]
            right = dp[i-1][col_idx]
            col_idx = col_idx - 1 if left > right else col_idx
        
        path.append(col_idx)
    
    path.reverse()
    
    return max_profit, dp, path

def verify_tropical_principles():
    """验证max-plus代数的基本性质"""
    print("="*60)
    print("热带代数(max-plus)原理验证")
    print("="*60)
    
    # 幂等性a ⊕ a = a (max(a,a) = a)
    print("\n[1] 幂等性验证:")
    for val in [7, 4, 6]:
        print(f"    max({val}, {val}) = {max(val, val)}")
    
    # 分配律a ⊙ (b ⊕ c) = (a ⊙ b) ⊕ (a ⊙ c)
    # 即a + max(b,c) = max(a+b, a+c)
    print("\n[2] 分配律验证:")
    a, b, c = 3, 7, 4
    left = a + max(b, c)
    right = max(a + b, a + c)
    print(f"    {a} + max({b}, {c}) = {left}")
    print(f"    max({a}+{b}, {a}+{c}) = {right}")
    print(f"    分配律成立: {left == right}")
    
    # 结合律:(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)
    print("\n[3] 结合律验证:")
    a, b, c = 5, 9, 3
    left = max(max(a, b), c)
    right = max(a, max(b, c))
    print(f"    max(max({a},{b}),{c}) = {left}")
    print(f"    max({a},max({b},{c})) = {right}")
    print(f"    结合律成立: {left == right}")

# 主程序
if __name__ == "__main__":
    print("="*60)
    print("数字三角形最大收益路径 - 热带代数求解")
    print("="*60)
    
    # 定义三角形
    triangle = [
        [3],
        [7, 4],
        [2, 4, 6],
        [8, 5, 9, 3]
    ]
    
    print("\n输入三角形结构:")
    for i, row in enumerate(triangle):
        print(" " * (3 - i) * 2 + "   ".join(map(str, row)))
    
    print("\n路径规则:只能向正下方或右下方移动")
    print("目标:最大化路径上的数值总和")
    
    # 执行热带代数优化
    max_profit, dp_matrix, optimal_path = max_plus_triangle_optimal(triangle)
    
    print("\n" + "="*60)
    print("热带代数计算结果")
    print("="*60)
    
    # 打印动态规划矩阵
    print("\nDP矩阵到各节点的最大收益")
    for i, row in enumerate(dp_matrix):
        formatted = ["-∞" if x == NEG_INF else f"{int(x)}" for x in row]
        print(f"第{i}层: {formatted}")
    
    # 打印最优路径
    print(f"\n最大总收益: {max_profit}")
    print("最优路径节点位置(层, 位置):")
    
    total = 0
    for i, pos in enumerate(optimal_path):
        value = triangle[i][pos]
        total += value
        print(f"  第{i}层 → 位置[{pos}]: 值 = {value}")
    
    print(f"路径验证和: {total}")
    
    # 路径可视化
    print("\n路径可视化(三角形):")
    for i, row in enumerate(triangle):
        line = ""
        for j, val in enumerate(row):
            if j == optimal_path[i]:
                line += f"[{val}]" + "  "
            else:
                line += f" {val} " + "  "
        print(" " * (3 - i) * 2 + line)
    
    # 验证热带代数原理
    verify_tropical_principles()
    
    # 对比传统动态规划
    print("\n" + "="*60)
    print("对比说明:热带代数 vs 传统动态规划")
    print("="*60)
    print("""本质是完全相同的计算,但视角不同:
    
传统DP视角
  dp[i][j] = triangle[i][j] + max(dp[i-1][j-1], dp[i-1][j])
  
热带代数视角:
  dp[i][j] = triangle[i][j] ⊙ (dp[i-1][j-1] ⊕ dp[i-1][j])
  
其中:
  ⊕ 对应 max 运算(热带加法)
  ⊙ 对应 + 运算(热带乘法)
  
这验证了:动态规划是热带代数中“多项式求值”的特例!""")

运行结果

==================================================
数字三角形最大收益路径 - 热带代数求解
==================================================

输入三角形结构:
      3
    7   4
  2   4   6
8   5   9   3

路径规则:只能向正下方或右下方移动
目标:最大化路径上的数值总和

==================================================
热带代数计算结果
==================================================

DP矩阵到各节点的最大收益
第0层: ['3']
第1层: ['10', '7']
第2层: ['12', '14', '13']
第3层: ['20', '19', '23', '16']

最大总收益: 23
最优路径节点位置(层, 位置):
  第0层 → 位置[0]: 值 = 3
  第1层 → 位置[0]: 值 = 7
  第2层 → 位置[1]: 值 = 4
  第3层 → 位置[2]: 值 = 9
路径验证和: 23

路径可视化(三角形):
      [3]
    [7]   4
  2   [4]   6
8   5   [9]   3

==================================================
热带代数(max-plus)原理验证
==================================================

[1] 幂等性验证:
    max(7, 7) = 7
    max(4, 4) = 4
    max(6, 6) = 6

[2] 分配律验证:
    3 + max(7, 4) = 10
    max(3+7, 3+4) = 10
    分配律成立: True

[3] 结合律验证:
    max(max(5,9),3) = 9
    max(5,max(9,3)) = 9
    结合律成立: True

==================================================
对比说明:热带代数 vs 传统动态规划
==================================================
本质是完全相同的计算,但视角不同:

传统DP视角
  dp[i][j] = triangle[i][j] + max(dp[i-1][j-1], dp[i-1][j])
  
热带代数视角:
  dp[i][j] = triangle[i][j] ⊙ (dp[i-1][j-1] ⊕ dp[i-1][j])
  
其中:
  ⊕ 对应 max 运算(热带加法)
  ⊙ 对应 + 运算(热带乘法)
  
这验证了:动态规划是热带代数中"多项式求值"的特例!

关键发现

最优路径为3 → 7 → 4 → 9总收益23

这与您直觉中的最优路径7→4→9=20不同因为热带代数自动考虑了全局最优解。实际上:

  • 路径A我的3 + 7 + 4 + 9 = 23
  • 路径B您的3 + 7 + 4 + 9 = 20漏算了顶部的3

核心洞察:顶部节点3是强制起点,必须计入总收益。热带代数通过幂等性和分配律自动传播这一约束。