[线性 DP、单调队列优化 DP] P1725 - 单调队列优化线性 DP
题目
相关题型:
- LC: 2944. 购买水果需要的最少金币数 (中等)
- LG: P3957 - 跳房子 (困难-)
输入 \(n, L, R,(1 ≤ L ≤ R ≤ n ≤ 2e5)\) 和长为 \(n + 1\) 的数组 \(a, (-1e3 ≤ a[i] ≤ 1e3)\),下标从 \(0\) 开始,同时保证 \(a[0] = 0\)。
总共有 \(n + 1\) 个格子,编号从 \(0\) 到 \(n\)。
- 你将从第 \(0\) 个格子向右跳,对于当前处于第 \(i\) 个格子时,你可以跳到
[i + L, i + R]中的任意格子中去。 - 当你跳到第 \(i\) 个格子后,你的得分增加 \(a[i]\)。
如果跳出边界(\(i > n\)),那么游戏结束,输出游戏结束后你的最大总得分。
线性 DP 标准模板题
- 定义 \(f[i]\):到达位置 \(i\) 时能够获得的最大价值和;
- 状态转移方程:
\[f[i] = \max_{j=i-R}^{i-L}(f[j]) + a[i]
\]
考虑优化:
- \(f[i]\) 转移自 \(j ∈ [i-R, i-L]\) 中的最大值 \(f[j]\);
- \(f[i+1]\) 转移自 \(j ∈ [i-R+1, i-L+1]\) 中的最大值 \(f[j]\);
- \(f[i+2]\) 转移自 \(j ∈ [i-R+2, i-L+2]\) 中的最大值 \(f[j]\)
...
后面的区间都能通过 上一个区间右移一个单位 得到,同时维护窗口中的最大值,我们能够自然的想到:单调队列。
// 单调队列的逻辑
deque<int> q;
for (int i = 0; i < n; i++) {
// 维护右端单调性
while (!q.empty() && f[q.back()] <= f[i]) {
q.pop_back();
}
// 维护左端边界
if (!q.empty() && q.front() < i - L) {
q.pop_front();
}
// 所有的端点都会入队一次
q.push_back(i);
// 按照题设要求更新答案
if (is_true) {
...q.front(); // 使用最大值更新答案
}
}
方法一:线性 DP
思路:
- 定义 \(f[i]\):到达位置 \(i\) 时能够获得的最大价值和;
- 状态转移方程:
\[f[i] = \max_{j=i-R}^{i-L}(f[j]) + a[i]
\]
代码
关键解题代码如下:
#define mst(f, x) memset(f, x, sizeof(f))
const int MAXN = 2e5 + 1;
int N, a[MAXN];
ll f[MAXN + 1];
void solve() {
int L, R;
cin >> N >> L >> R;
for (int i = 0; i <= N; i++) {
cin >> a[i];
}
// 初始化极小值
mst(f, 128); // 每个字节赋值 128->0x80 会溢出;
f[0] = 0; // 边界值初始化, f[0] = 0;
for (int i = 0; i <= N; i++) {
// 约束 j 的范围
for (int j = min(i + L, N + 1); j <= min(i + R, N + 1); j++) {
f[j] = max(f[j], f[i] + a[j]);
}
}
cout << f[N + 1] << endl;
}
复杂度
- 时间复杂度:\(O(n^2)\)。
- 空间复杂度:\(O(n)\)。
方法二:单调队列优化 DP
从状态转移方程:
\[f[i] = \max_{j=i-R}^{i-L}(f[j]) + a[i]
\]
可以看出当前的 \(f[i]\) 只与 \(j ∈ [i-R, i-L]\) 有关。而且每次 \(j\) 的范围只会向右移一位,因此我们可以用 单调队列 维护 \(f[j]\) 的单调最大值。
代码
void solve() {
int L, R;
cin >> N >> L >> R;
for (int i = 0; i <= N; i++) {
cin >> a[i];
}
// 初始化极小值
mst(f, 128); // 每个字节赋值 128->0x80 会溢出;
f[0] = 0;
deque<int> q; // 双向队列
ll ans = -infl;
for (int i = L; i <= N; i++) {
// 维护区间右端单调性
while (!q.empty() && f[q.back()] <= f[i-L]) {
q.pop_back();
}
// 维护左端区间边界值
if (!q.empty() && q.front() < i - R) {
q.pop_front();
}
q.push_back(i-L); // 入队
f[i] = f[q.front()] + a[i]; // 更新 f[i]
if (i + R > N) { // 更新答案
chmax(ans, f[i]);
}
}
cout << ans << endl;
}
复杂度
- 时间复杂度:\(O(n)\)。
- 空间复杂度:\(O(n)\)。

浙公网安备 33010602011771号