区间 DPCSP-S

目录

💡 核心思想

区间 DP 的状态定义在区间 $[l, r]$ 上,通过枚举区间内的分割点来合并子区间的结果。典型特征是"大区间的解由小区间的解合并得到"。经典问题:石子合并、矩阵链乘法。

区间 DP 的核心就是"从小区间推大区间"。状态 $dp[l][r]$ 表示区间 $[l,r]$ 上的最优值,转移时枚举分界点 $k$:$dp[l][r] = \min_{k} (dp[l][k] + dp[k+1][r] + cost)$。遍历顺序是按区间长度从小到大,这点千万别搞反了。

🎯 直觉理解

想象你要把一堆石子合并成一堆。每次只能合并相邻的两堆,代价是两堆石子的重量之和。你要找到一个合并顺序使总代价最小。这就是"先解决小段,再合并成大段"的思路。

📝 算法流程

  1. 状态:$dp[l][r]$ = 区间 $[l,r]$ 的最优值
  2. 转移:枚举分界点 $k$,合并左右两部分
  3. 遍历:外层枚举区间长度 len,内层枚举左端点 l
  4. 初始化:$dp[i][i] = $ 边界值

$$dp[l][r] = \min_{k=l}^{r-1} \{dp[l][k] + dp[k+1][r]\} + cost(l,r)$$

📊 复杂度分析

指标复杂度
时间$O(n^3)$
空间$O(n^2)$

💻 参考实现(C++)

C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; cin >> n;
    vector<int> a(n+1), s(n+1, 0);
    for (int i = 1; i <= n; i++) { cin >> a[i]; s[i] = s[i-1] + a[i]; }
    vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
    for (int len = 2; len <= n; len++) {
        for (int l = 1; l + len - 1 <= n; l++) {
            int r = l + len - 1;
            dp[l][r] = INT_MAX;
            for (int k = l; k < r; k++)
                dp[l][r] = min(dp[l][r], dp[l][k] + dp[k+1][r] + s[r] - s[l-1]);
        }
    }
    cout << dp[1][n] << endl;
    return 0;
}

⚠️ 常见坑点

遍历顺序错误——必须按区间长度从小到大

区间边界 r = l+len-1 计算错误

忘记加区间本身的代价

四边形不等式优化可降到 $O(n^2)$ 但竞赛中通常不需要

📚 相关题目

题目来源难度备注
P1775 石子合并洛谷CSP-S区间DP模板
P3146 牛的区间DP洛谷CSP-S区间DP变体