diff --git a/solutions/0019.CountingSundays/euler_19.py b/solutions/0019.CountingSundays/euler_19.py new file mode 100644 index 0000000..e11e4ca --- /dev/null +++ b/solutions/0019.CountingSundays/euler_19.py @@ -0,0 +1,84 @@ +""" +You are given the following information, but you may prefer to do some research for yourself. + +- 1 Jan 1900 was a Monday. +- Thirty days has September, April, June and November. +- All the rest have thirty-one, Saving February alone, +- Which has twenty-eight, rain or shine. And on leap years, twenty-nine. +- A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400. + +How many Sundays fell **on the first of the month** during the twentieth century (1 Jan 1901 to 31 Dec 2000)? +""" + +import time + + +def timer(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"Execution time: {end_time - start_time} seconds") + return result + + return wrapper + + +def is_leap_year(year: int) -> bool: + return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0) + + +def days_in_years(years: list[int]) -> int: + leapyears = [year for year in years if is_leap_year(year)] + return sum([366 if year in leapyears else 365 for year in years]) + + +def count_sundays(start_year: int | str, end_year: int | str) -> int: + start_year = int(start_year) if isinstance(start_year, str) else start_year + end_year = int(end_year) if isinstance(end_year, str) else end_year + if start_year < 1900 or start_year > end_year: + raise ValueError(f"Invalid year range {start_year} to {end_year}") + last_days = days_in_years(list(range(1900, start_year))) + year_list = list(range(start_year, end_year + 1)) + days = days_in_years(year_list) + (last_days % 7) + sundays = days // 7 + return sundays + + +def days_month(year: int, month: int) -> int: + if month in [1, 3, 5, 7, 8, 10, 12]: + return 31 + elif month in [4, 6, 9, 11]: + return 30 + elif month == 2: + return 29 if is_leap_year(year) else 28 + else: + raise ValueError(f"Invalid month {month}") + + +def days_sunday_month(start_year: int, end_year: int) -> int: + count = 0 + outday = days_in_years(list(range(1900, start_year))) % 7 + for year in range(start_year, end_year + 1): + for month in range(1, 13): + if outday == 6: + count += 1 + outday = (outday + days_month(year, month)) % 7 + return count + + +@timer +def main() -> None: + print(f"Number of Sundays between 1901 and 2000: {count_sundays(1901, 2000)}") + + +@timer +def main_first_day() -> None: + print( + f"Number of Sundays on the first of the month between 1901 and 2000: {days_sunday_month(1901, 2000)}" + ) + + +if __name__ == "__main__": + main() + main_first_day() diff --git a/solutions/0067.MaxPathSum2/HowTo.md b/solutions/0067.MaxPathSum2/HowTo.md new file mode 100644 index 0000000..fe8b4ae --- /dev/null +++ b/solutions/0067.MaxPathSum2/HowTo.md @@ -0,0 +1,118 @@ +好的,这是一个非常有趣且前沿的数学/优化问题。将优化问题转换为 **Tropical Semiring(热带半环)** 的框架,本质上是利用热带几何的代数结构来重新表述和解决组合优化、动态规划、甚至某些连续优化问题。 + +下面我将做一个详细的介绍,从基础概念到转换方法,再到应用和意义。 + +### 1. 什么是 Tropical Semiring? + +Tropical Semiring 有两种常见形式,它们本质上是同构的: + +* **Max-Plus 代数(热带半环的常见形式)**: + * **集合**: ℝ ∪ { -∞ } + * **“加法” ⊕**: `a ⊕ b = max(a, b)` + * **“乘法” ⊗**: `a ⊗ b = a + b` + * **加法单位元 0_T**: `-∞` (因为 `max(a, -∞) = a`) + * **乘法单位元 1_T**: `0` (因为 `a + 0 = a`) + +* **Min-Plus 代数**: + * **集合**: ℝ ∪ { +∞ } + * **“加法” ⊕**: `a ⊕ b = min(a, b)` + * **“乘法” ⊗**: `a ⊗ b = a + b` + * **加法单位元 0_T**: `+∞` (因为 `min(a, +∞) = a`) + * **乘法单位元 1_T**: `0` (因为 `a + 0 = a`) + +**核心思想**:在热带代数中,我们**把传统的“加法”替换为“取最大/最小值”,把传统的“乘法”替换为“加法”**。这种转换使得一类特定的优化问题可以“线性化”。 + +### 2. 为什么这种转换有用? + +在经典代数中,优化问题(如求最小路径、最优调度)通常涉及 `min`/`max` 和 `+` 运算。这些运算在经典代数中是**非线性**的,但在热带代数中,它们恰好对应于半环的 **线性运算**(⊕ 和 ⊗)! +这使得我们可以: +1. **利用线性代数工具**:在热带代数意义下,我们可以定义矩阵、向量,并进行“线性”运算。 +2. **揭示问题的组合结构**:热带多项式、热带超曲面与组合优化问题的解空间有深刻联系。 +3. **统一分析算法**:动态规划、最短路径算法等,本质上是在执行热带矩阵乘法。 + +### 3. 如何转换:一个详细的步骤和例子 + +我们以一个最经典的问题为例:**单源最短路径问题**。 + +#### **问题描述(经典形式)**: +给定一个有向图 G=(V, E),边权为 `w(i->j)`,求从源点 `s` 到所有其他节点 `v` 的最短路径距离 `d(v)`。 + +#### **转换步骤**: + +**步骤 1:识别运算对** +观察最短路径问题的核心递推式(Bellman 方程): +`d(v) = min_{u: u->v存在} [ d(u) + w(u->v) ]`, 且 `d(s) = 0`。 +我们识别出关键运算对:**`(min, +)`**。这正是 **Min-Plus 代数**的 `(⊕, ⊗)`。 + +**步骤 2:用热带代数符号重写** +* 将 `min` 重写为 ⊕ (Min-Plus意义下)。 +* 将 `+` 重写为 ⊗。 +* 将初始化 `d(s) = 0` 写成:`d_s = 1_T = 0`(乘法单位元)。其他节点初始距离为 `0_T = +∞`。 + +则 Bellman 方程变为一个漂亮的“线性”方程: +`d(v) = ⊕_{(u->v)} [ d(u) ⊗ w(u->v) ]` +或者更紧凑地,对于所有 `v ≠ s`,有: +`d(v) = min_u { d(u) + w(u, v) }` 在经典意义上,等价于 `d = d ⊗ A` 在热带代数意义上?稍等,我们需要更精确。 + +**步骤 3:构建热带矩阵和向量** +定义 **邻接矩阵 A**,其元素 `A_{ij}` 在热带代数(Min-Plus)下为: +* `A_{ij} = w(i->j)`,如果边 `i->j` 存在。 +* `A_{ij} = 0_T = +∞`,如果边 `i->j` 不存在。 +* `A_{ii} = 1_T = 0`?(这里通常设为0,表示零长度自环,但有时也设为+∞以避免自环影响,依问题而定)。 + +定义 **距离向量 x^{(k)}**,其中 `x^{(k)}_i` 表示从源点 `s` 出发,经过**最多 k 条边**到达节点 `i` 的最短距离。 + +**步骤 4:将问题表述为热带线性方程或不动点问题** +初始化:`x^{(0)}` 是一个向量,其中 `x^{(0)}_s = 0`,其他为 `+∞`。 + +**核心洞察**:从 `s` 到 `v` 的、最多经过 `k+1` 条边的最短路径,要么是原来 `k` 条边内的路径,要么是走到某个前驱 `u` 的 `k` 步最优路径再加上边 `u->v`。这正好是热带矩阵乘法! + +`x^{(k+1)} = x^{(k)} ⊗ A`, **但这里需要小心定义**。 +更标准的写法是:`x^{(k+1)}_j = min_i { x^{(k)}_i + A_{ij} }`。 +这正好是 **`x^{(k+1)} = x^{(k)} ⊗_{trop} A`**,其中 `⊗_{trop}` 是热带矩阵乘法。 +由于我们要取所有可能步数中的最小值,最终解是: +`d = x^{(0)} ⊕ x^{(1)} ⊕ x^{(2)} ⊕ ... = x^{(0)} ⊕ (x^{(0)}⊗A) ⊕ (x^{(0)}⊗A^2) ⊕ ...` + +**步骤 5:求解与经典算法的对应** +这个无穷和 `x^{(0)} ⊗ (I ⊕ A ⊕ A^2 ⊕ A^3 ⊕ ...)` 在图中无负环的情况下,会在最多 `n-1` 步后稳定(因为最短路径不会重复访问节点)。这个级数就是热带意义下的 **Kleene 星算子** `A*`。 + +最终,最短路径向量 `d` 可以写成: +`d = x^{(0)} ⊗ A*`,其中 `A* = I ⊕ A ⊕ A^2 ⊕ ... ⊕ A^{n-1}`。 + +**计算 `A*`** 的过程,在热带代数下,等价于经典的 **Floyd-Warshall 算法**。而迭代计算 `x^{(k+1)} = x^{(k)} ⊗ A` 直到收敛,就是 **Bellman-Ford 算法** 的紧凑数学表述。 + +### 4. 更广泛的应用与转换模式 + +1. **动态规划问题**: + * 任何具有“最优子结构”和“重叠子问题”的DP,其递推式通常形如 `dp(state) = opt_{choice} { dp(prev_state) + cost(choice) }`。 + * 这里的 `opt` 是 `min` 或 `max`,`+` 是代价的累积。这天然就是热带线性递推。 + * **例子**:维特比算法(序列解码)、编辑距离、资源调度。 + +2. **离散事件系统(DES)**: + * 用于建模制造系统、交通网络。系统的“事件”(如零件加工完成)时间由最慢的前置环节决定。 + * 状态演化方程:`x(k+1) = A ⊗ x(k)`,其中 `x_i(k)` 是第 `k` 个事件在节点 `i` 的发生时间,`A_{ij}` 是从 `j` 到 `i` 的活动时间。这是一个 **Max-Plus 线性系统**。 + +3. **组合优化与热带几何**: + * 一个热带多项式 `P(x) = max_{i} ( a_i + i*x )` 或 `min_{i} ( a_i + i*x )`,其“根”(使得最大值在两个或更多项同时达到的 x)的集合定义了**热带超曲面**。 + * 求解 `P(x)` 的最小/最大值点,与寻找该超曲面的几何结构相关。这联系了代数几何和优化。 + +4. **神经网络与机器学习**: + * 在某些特定结构(如带有 ReLU 和加性操作的网络)中,推理过程可以被解释为热带有理函数(热带多项式的商)的求值。 + * 这为理解某些神经网络的决策边界提供了新颖的数学视角。 + +### 5. 总结与意义 + +将优化问题转换为 Tropical Semiring 的过程,可以概括为以下模式: + +1. **识别运算核**:在问题的目标函数或约束中,找到核心的 `(min/max, +)` 运算对。 +2. **代数重述**:用 `(⊕, ⊗)` 分别替换 `(min/max, +)`,将问题中的常数映射为热带半环的单位元(0_T, 1_T)。 +3. **构建热带对象**:将变量、系数、权重等组织成热带向量、矩阵或多项式。 +4. **表述为热带方程**:将原优化问题写成一个热带线性方程组、矩阵特征值问题(`A ⊗ x = λ ⊗ x`)、或多项式求根/求值问题。 +5. **利用热带工具求解**:使用热带版本的线性代数算法(如高斯消元法、牛顿法)、Kleene星迭代,或几何方法(分析热带超曲面)来求解。 + +**这种转换的意义在于**: +* **理论统一**:它为一大类看似不相关的算法(最短路径、动态规划、调度算法)提供了一个统一的代数框架。 +* **新视角与新工具**:它允许将成熟的线性代数理论和几何直觉引入组合优化领域,从而可能推导出新的算法或复杂性结论。 +* **跨学科连接**:它在操作研究、计算机科学、离散数学、代数几何和系统理论之间建立了深刻的联系。 + +总之,Tropical Semiring 不仅是一个数学上的巧妙构造,更是一个强大的“透镜”,通过它,许多复杂的优化问题会呈现出令人惊讶的简洁和结构化的形式。 diff --git a/solutions/0067.MaxPathSum2/热带代数解法.md b/solutions/0067.MaxPathSum2/tropical_semiring.md similarity index 95% rename from solutions/0067.MaxPathSum2/热带代数解法.md rename to solutions/0067.MaxPathSum2/tropical_semiring.md index ebc07be..5844fec 100644 --- a/solutions/0067.MaxPathSum2/热带代数解法.md +++ b/solutions/0067.MaxPathSum2/tropical_semiring.md @@ -1,262 +1,262 @@ -对于数字三角形路径问题,这正是**max-plus代数**的经典应用场景。我将使用**自顶向下**和**自底向上**两种热带代数视角来求解。 - -```python -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是强制起点**,必须计入总收益。热带代数通过幂等性和分配律自动传播这一约束。 +对于数字三角形路径问题,这正是**max-plus代数**的经典应用场景。使用**自顶向下**和**自底向上**两种热带代数视角来求解。 + +```python +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是强制起点**,必须计入总收益。热带代数通过幂等性和分配律自动传播这一约束。