算法知识图谱总览 / 动态规划基础族 / 区间DP

区间DP

P0intermediatedeepG-A-11

What · 是什么

f(i,j)表示区间[i,j]最优,枚举合并点k,O(n^3)

区间DP是一种以区间为状态定义的动态规划:状态 f(i,j) 表示将区间 [i,j] 内所有元素通过某种合并方式处理后的最优值。转移时枚举合并点 k,将 [i,k] 和 [k+1,j] 两部分的最优解合并。典型问题如石子合并、矩阵链乘法。

Why · 为什么

直觉 · Intuition

当问题本质是「将若干部分合并」时,最终的最优合并方案一定是在某个位置 k 处将区间分成左右两部分分别合并——这就是区间DP的决策点。由于左右子区间的长度都严格小于原区间,按长度递增枚举即可保证无后效性。

原理 · Principle

状态转移方程:f(i,j)=max{f(i,k)+f(k+1,j)+cost(i,j)},其中 i<=k<j,cost(i,j) 为合并 [i,j] 两部分的代价(如区间和)。按区间长度 len 从 2 到 n 枚举,再枚举起点 i(j=i+len-1),最后枚举合并点 k。时间复杂度 O(n³)。环上问题将数组复制为 2n,在长度 2n 的链上做区间DP,最后取 min/max f(1,n)...f(n,2n-1)。

历史 · History

区间DP是矩阵链乘法(1960s)和最优括号化问题的推广。其核心思想——按区间长度递增枚举、枚举合并点——已成为竞赛中合并类问题的标准范式。石子合并(NOI1995)是其经典教学题。

How · 实现

伪代码

for len in 2..n:
  for i in 1..(n-len+1):
    j = i + len - 1
    for k in i..(j-1):
      f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + cost(i,j))

Python 实现

def stone_merge(a):
    """石子合并(环形) 求最大得分
    a: 各堆石子数量, 环形排列
    返回: 最大合并得分
    """
    n = len(a)
    # 环形 -> 断环成链, 复制为 2n
    b = a + a
    N = 2 * n
    s = [0] * (N + 1)  # 前缀和
    for i in range(1, N + 1):
        s[i] = s[i - 1] + b[i - 1]

    INF = float('-inf')
    f = [[0] * (N + 1) for _ in range(N + 1)]

    for length in range(2, n + 1):  # 枚举区间长度
        for i in range(1, N - length + 2):  # 枚举起点
            j = i + length - 1
            f[i][j] = INF
            for k in range(i, j):  # 枚举合并点
                val = f[i][k] + f[k + 1][j] + s[j] - s[i - 1]
                if val > f[i][j]:
                    f[i][j] = val

    ans = INF
    for i in range(1, n + 1):
        if f[i][i + n - 1] > ans:
            ans = f[i][i + n - 1]
    return ans


def stone_merge_min(a):
    """石子合并(环形) 求最小得分"""
    n = len(a)
    b = a + a
    N = 2 * n
    s = [0] * (N + 1)
    for i in range(1, N + 1):
        s[i] = s[i - 1] + b[i - 1]

    INF = float('inf')
    f = [[0] * (N + 1) for _ in range(N + 1)]

    for length in range(2, n + 1):
        for i in range(1, N - length + 2):
            j = i + length - 1
            f[i][j] = INF
            for k in range(i, j):
                val = f[i][k] + f[k + 1][j] + s[j] - s[i - 1]
                if val < f[i][j]:
                    f[i][j] = val

    ans = INF
    for i in range(1, n + 1):
        if f[i][i + n - 1] < ans:
            ans = f[i][i + n - 1]
    return ans


if __name__ == "__main__":
    # 石子合并: 4堆 [4,5,9,4] 环形
    a = [4, 5, 9, 4]
    # 链上合并: 4+5=9(+9), 9+9=18(+18), 9+4=13(+13) -> varies
    # 最大得分 = 54, 最小得分 = 43 (经典答案)
    print(f"max = {stone_merge(a)}")  # 54
    print(f"min = {stone_merge_min(a)}")  # 43
    assert stone_merge(a) == 54
    assert stone_merge_min(a) == 43
    print("All tests passed.")
PyPy 提示PyPy 对三层嵌套循环优化较好,n=200 的 O(n³) 约 0.1s;n>400 时需考虑四边形不等式优化到 O(n²)。列表初始化用 [[0]*(N+1) for _ in range(N+1)]。
时间复杂度O(n³) — 三重循环(长度×起点×合并点);可用四边形不等式优化至 O(n²)
空间复杂度O(n²) — 二维DP表;环形复制为 2n
注意 · 局限O(n³) 在 n>500 时 Python 必 TLE(C++ 也勉强)。四边形不等式优化可降至 O(n²) 但实现复杂。环上问题需 2n 空间。Python 建议 n<=200 使用。

易错点

例题

OJ题号难度考点链接
LOJ101472NOI1995 石子合并:环形区间DP模板题https://loj.ac/problem/10147
LuoguP10632NOIP2006 能量项链:区间DP,合并代价为左端点×分割点×右端点https://www.luogu.com.cn/problem/P1063
LuoguP10053NOIP2007 矩阵取数游戏:每行独立做区间DP,高精度加法https://www.luogu.com.cn/problem/P1005
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
上游 · 本知识点依赖(前置/变体/组合/共现)

信源

✓ = 已核验 · △ = 未核验