84
solutions/0019.CountingSundays/euler_19.py
Normal file
84
solutions/0019.CountingSundays/euler_19.py
Normal file
@@ -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()
|
||||||
118
solutions/0067.MaxPathSum2/HowTo.md
Normal file
118
solutions/0067.MaxPathSum2/HowTo.md
Normal file
@@ -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 不仅是一个数学上的巧妙构造,更是一个强大的“透镜”,通过它,许多复杂的优化问题会呈现出令人惊讶的简洁和结构化的形式。
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
对于数字三角形路径问题,这正是**max-plus代数**的经典应用场景。我将使用**自顶向下**和**自底向上**两种热带代数视角来求解。
|
对于数字三角形路径问题,这正是**max-plus代数**的经典应用场景。使用**自顶向下**和**自底向上**两种热带代数视角来求解。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import numpy as np
|
import numpy as np
|
||||||
Reference in New Issue
Block a user