[线性 DP、单调队列优化 DP] P1725 - 单调队列优化线性 DP

题目

洛谷 P1725

相关题型:

  1. LC: 2944. 购买水果需要的最少金币数 (中等)
  1. 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] \]

考虑优化:

  1. \(f[i]\) 转移自 \(j ∈ [i-R, i-L]\) 中的最大值 \(f[j]\)
  2. \(f[i+1]\) 转移自 \(j ∈ [i-R+1, i-L+1]\) 中的最大值 \(f[j]\)
  3. \(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)\)
posted @ 2025-04-18 23:39  kris4js  阅读(44)  评论(0)    收藏  举报