DP优化学习笔记

2026-02-24

1 前缀和优化DP

1.1 核心思想

用空间换时间

通过预处理前缀和数组,将求和操作转化为前缀和数组的两次访问

1.2 常见可优化类型

1.2.1 一维DP:区间求和

状态定义:
\(dp[i]\) 表示考虑前 \(i\) 个元素或以 \(i\) 结尾的答案

转移方程:

\[dp[i] = \sum_{j = L[i]}^{R[i]} g(j) + C[i]$ \]

优化:

预处理 \(g(j)\) 的前缀和

\[sum[j] = \sum_{x=0}^{j} g(x) \]

\(dp[i] = sum[R[i]] - sum[L[i]-1] + C[i]\)

1.2.2 二维DP:由上一层的连续区间转移

状态定义:

\(dp[i][j]\) 表示处理完前 \(i\) 个物品,当前状态为 \(j\) 的答案

转移方程形式:

\[dp[i][j] = \sum_{k = L}^{R} dp[i-1][k] \]

优化:

在计算第 \(i\) 层之前,先预处理出第 \(i-1\) 层状态的前缀和

\[sum[i-1][k] = \sum_{x=0}^{k} dp[i-1][x] \]

\(dp[i][j] = sum[i-1][R] - sum[i-1][L-1]\)

1.2 习题讲解

1.2.1 跳跃游戏 (Jump Game)

链接 : 暂无

1.2.1.1 暴力DP思路

状态定义:

\(dp[i]\) 表示是否能到达站点 \(i\)

转移方程:

\(dp[i] = true\),如果存在一个 \(j\) 满足 \(j\) 可到达,\(S[j]=='0'\),且 \(i\)\(j\) 的跳跃范围内

1.2.1.2 前缀和优化分析

观察:

对于不同的 \(i\):

\(dp[i]\) 的值总是由上一段连续区间内的 \(dp\) 值之和决定

优化方法:
维护一个前缀和数组 \(preSum\)

\[preSum[i] = \sum_{x=0}^{i} dp[x] \]

那么,\(dp[i]\) 所需区间的和可以 \(O(1)\) 计算:

\(区间和 = preSum[i - minJump] - preSum[max(0, i - maxJump) - 1]\) 注意处理边界

这样就将 1D/1D 的DP优化为了 1D/0D

1.2.2 Candies

链接 : AtCoder: DP_M - Candies

1.2.2.1 暴力DP思路

状态定义:

\(dp[i][j]\) 表示分配到第 \(i\) 个孩子,已经分掉了 \(j\) 颗糖的方案数。

转移方程:
\(i\) 个孩子拿 \(k\) 颗糖,则前 \(i-1\) 个孩子分掉了 \(j-k\) 颗糖

\[dp[i][j] = \sum_{k = max(0, j-a_i)}^{j} dp[i-1][j-k] \]

复杂度:
\(O(N * K^2)\),会超时

1.2.2.2 前缀和优化分析

观察:

\(dp[i][j]\) 的值由第 \(i-1\) 层状态的 一段连续区间 \([max(0, j-a_i), j]\) 的和决定

优化方法:

在计算第 \(i\) 行之前,预处理第 \(i-1\) 行的前缀和数组 \(sum_prev\)

\[sum_{prev}[x] = \sum_{t=0}^{x} dp[i-1][t] \]

那么转移变为:

\(dp[i][j] = sum_{prev}[j] - sum_{prev}[max(0, j-a_i) - 1]\)

注意边界,当下标为-1时,值为0

复杂度降为 \(O(N * K)\)

1.2.3 逆序对数列

链接 : Luogu: P2513 [HAOI2009] 逆序对数列

刷表法见 2.3.2

1.2.3.1 填表法DP思路

状态定义:

\(dp[i][j]\) 表示用前 \(i\) 个数组成的排列,逆序对数为 \(j\) 的方案数。

转移方程:

插入到最后一个位置,新增 \(0\) 个逆序对
插入到倒数第二个,新增 \(1\) 个逆序对
...
插入到第一个,新增 \(i-1\) 个逆序对

\[dp[i][j] = \sum_{k=0}^{min(j, i-1)} dp[i-1][j-k] \]

1.2.3.2 前缀和优化分析

观察:

与Candies问题惊人地相似!
\(dp[i][j]\) 仍然是由第 \(i-1\) 层的一段连续区间求和得到
区间的长度受 \(i-1\) 限制

优化方法:

同样,预处理第 \(i-1\) 层的前缀和

\[sum[t] = \sum_{x=0}^{t} dp[i-1][x] \]

则转移可优化为:

dp[i][j] = sum[j] - (j-i >= 0 ? sum[j-i] : 0)

复杂度从 \(O(n^2 * k)\) 降为 \(O(n * k)\)

1.2.4 ABC253E Distance Sequence

链接 : AtCoder: ABC253_E Distance Sequence

1.2.4.1 暴力DP思路

状态定义:

\(dp[i][j]\) 表示第 \(i\) 个数字为 \(j\) 的方案数。

转移方程:

\[dp[i][j] = \sum_{l=1}^{j-K} dp[i-1][l] + \sum_{l=j+K}^{M} dp[i-1][l] \]

1.2.4.2 前缀和优化分析

观察:

这次需要求的是第 \(i-1\) 层状态的两段不连续区间的和。但这两段区间本身仍然是连续的

优化方法:

预处理第 \(i-1\) 层的前缀和

\[sum[x] = \sum_{t=1}^{x} dp[i-1][t] \]

则转移可优化为:

左区间和 = sum[max(0, j-K)]
右区间和 = sum[M] - sum[min(M, j+K-1)]
dp[i][j] = 左区间和 + 右区间和

复杂度从 \(O(N * M^2)\) 降为 \(O(N * M)\)

2026-02-25

2 线段树优化DP

2.1 核心思想

利用线段树的区间查询能力快速获得转移所需的决策集合,从而将每次转移的复杂度从 \(O(n)\) 降至 \(O(log_n)\)

2.2 常见可优化类型

2.2.1 1D/1D DP,按权值条件转移

转移方程形如:

\[dp[i] = 1 + \sum_{j < i, a[j] < a[i]} dp[j] \]

优化:

建立线段树
\(a[j]\) 为下标维护 \(dp[j]\) 的区间和
转移时查询小于 \(a[i]\) 的区间和,转移后更新 \(a[i]\) 位置的值。

2.2.2 2D/1D DP,按层转移加权值限制

转移方程形如:

\[dp[i][k] = \sum_{j < i, a[j] < a[i]} dp[j][k-1] \]

优化:

对每一层 \(k\) 建立一棵线段树
维护该层所有 \(dp[j][k-1]\)\(a[j]\) 的分布
转移时查询小于 \(a[i]\) 的区间和,转移后更新对应权值

2.3 习题讲解

2.3.1 严格上升子序列的数量

链接 : 暂无

2.3.1.1 暴力DP思路

状态定义:

\(dp[i]\) 表示以第 \(i\) 个元素结尾的严格上升子序列个数

转移方程:

\[dp[i] = 1 + \sum_{j < i, a[j] < a[i]} dp[j] \]

直接转移复杂度 \(O(n^2)\),不可行

2.3.1.2 线段树优化分析

观察:

转移只关心所有满足 \(a[j] < a[i]\)\(dp[j]\) 之和
这天然适合用权值线段树维护:

\(a\) 离散化后,线段树的每个叶子节点对应一个权值,维护该权值上所有 \(dp[j]\) 的和

转移过程:

  1. 查询线段树中区间 \([1, a[i]-1]\) 的和,得到 \(sum\)
  2. 计算 \(dp[i] = sum + 1\)
  3. \(dp[i]\) 累加到线段树中 \(a[i]\) 对应的位置上。

时间复杂度

\(O(n log n)\)

2.3.2 逆序对数列

链接 : Luogu: P2513 [HAOI2009] 逆序对数列

填表法见 1.2.3

PS : 嗨嗨嗨,没想到我又来了吧 O(∩∩)O

2.3.2.1 刷表法DP思路

状态定义:

\(dp[i][j]\) 表示用前 \(i\) 个数组成的排列,逆序对数为 \(j\) 的方案数。

转移方程:

插入到最后一个位置,新增 \(0\) 个逆序对
插入到倒数第二个位置,新增 \(1\) 个逆序对
……
插入到第一个位置,新增 \(i-1\) 个逆序对

\[dp[i][j] = \sum_{k=0}^{\min(j, i-1)} dp[i-1][j-k] \]

2.3.2.1 线段树优化刷表法

状态定义:

\(dp[i][j]\) 表示由 \(1 \sim i\) 组成的排列中,逆序对数量恰好为 \(j\) 的方案数

转移方程:

\[dp[i][j] = \sum_{t=0}^{\min(i-1,\, j)} dp[i-1][j-t] \]

其中 \(t\) 表示将数字 \(i\) 插入到长度为 \(i-1\) 的排列时新产生的逆序对数

2026-02-27

3 单调队列优化DP学习笔记

3.1 核心思想

核心思想:

维护一个双端队列
其中的元素按照其对应的权重 \(dp[j] + g(j)\) 单调排列,同时保证下标在窗口内
这样队头就是当前窗口的最优决策

3.2 单调队列 / 滑动窗口

链接 : Luogu: P1886 【模板】单调队列 / 滑动窗口

3.2.1 单调队列解法

维护一个队列:

  • 存储可能成为最值的元素下标
  • 保持队列中下标对应的值单调
  • 每次窗口移动时:
  1. 掐头:若队头下标已滑出窗口,弹出
  2. 取值:队头即为当前窗口最值
  3. 去尾:从队尾弹出所有比当前元素更劣的元素
  4. 入队:将当前下标加入队尾

时间复杂度:

\(O(n)\)

3.3 习题讲解

3.3.1 切蛋糕

链接 : Luogu: P1714 切蛋糕

3.3.1.1 DP 思路

\(sum[i]\) 为前缀和

\(i\) 结尾的最大子段和为:

\[dp[i] = \max_{i - m \le j \le i - 1} \{ sum[i] - sum[j] \} \]

分离变量:

\[dp[i] = sum[i] + \max_{j \in [i-m, i-1]} \{ -sum[j] \} \]

即求滑动窗口内 \(-sum[j]\) 的最大值,等价于求 \(sum[j]\) 的最小值

3.3.1.2 单调队列优化

维护一个单调递增队列,窗口长度为 \(m\)
遍历 \(i\) 时:

  • 掐头:若队头下标 \(< i - m\),弹出
  • 取值:\(ans = \max(ans, sum[i] - sum[q[front]])\)
  • 去尾:从队尾弹出所有 \(sum[q[tail]] \ge sum[i]\) 的元素
  • 入队:将 \(i\) 加入队尾

3.3.2 琪露诺

3.3.2.1 DP 思路

状态定义:

\(dp[i]\) 为到达点 \(i\) 的最大价值,则:

转移方程:

\[dp[i] = A[i] + \max_{i - R \le j \le i - L} dp[j] \]

转移区间是滑动窗口,符合单调队列条件

3.2.2 优化

用单调递减队列维护 \(dp[j]\) 的最大值

注意:

需要从 \(L\) 开始计算,且最终答案是 \(\max_{i > n - L} dp[i]\)

posted @ 2026-02-24 21:31  Sail-With-Dawn  阅读(35)  评论(0)    收藏  举报