DP 总结

ys,qd! / 2023-08-20 / 原文

DP

即动态规划(Dynamic Programming),是数学中比较冷门的学科,通过分解成若干个子问题合并而成。主要有:

  • 背包问题
  • 区间 DP
  • 状态压缩 DP

DP 特征:无后效性,最优子结构,子问题重叠。

背包问题:

0-1 背包

例题:luogu P1048 采药

  • 题意:

给出物品的重量 \(w_i\) 和价值 \(v_i\),每个物品只能选一次,问最大价值。

  • 思路:

设方程 \(f_{i,j}\) 为取前 \(i\) 个物品,容量为 \(j\) 时的最大价值。
\(f_{i,j}=\max\{f_{i-1,j}, f_{i-1,j-w_i}+v_i\}\)。什么意思呢:如果当前物品不选,那么直接从 \(i-1\) 转移过来(\(f_{i-1,j}\))。如果当前选,那么这个除了物品之外的总容量为 \(j - w_i\),呢我们直接调用 \(f_{i-1,j-w_i}\) 加上选的价值即可。

for (int i = 1; i <= m; i++) {
  for (int j = t; j >= w[i]; j--) {
      dp[j] = max(dp[j], dp[j - w[i]] + e[i]);
  }
}

多重背包

例题:luogu P1616 疯狂的采药

  • 题意:

给出物品的重量 \(w_i\) 和价值 \(v_i\),每个物品可以选无限次,问最大价值。

  • 思路:

考虑 \(O(n^3)\) 暴力,枚举 \(k\) 为选择物品的数量(\(k\le \dfrac{w_i}{j}\)),所以转移方程为:

\(f_{i,j}=\max\{f_{i-1,j\times k}+v_i\times k\}\)

怎么优化呢?

发现对于 \(f_{i,j-w_i}\)\(f_{i,j-w_i\times k}\) 转移过来,所以我们就不用枚举 \(k\) 了,即 \(f_{i,j}\)\(f_{i,j-w_i}\) 转移过来。

然后可以滚动数组优化掉第一维:

for (long long i = 1; i <= m; i++) {
  for (long long j = e[i]; j <= t; j++) {
    dp[j] = max(dp[j], dp[j - e[i]] + w[i]);
  }
}

完全背包

其实就朴素的多重背包。

转移方程:\(f_{i,j}=\max\limits_{k=0}^nk_i\{f_{i-1,j-w_i \times k}+v_i\times k\}\)

混合背包

就是上面三种背包的结合。判断一下做出相应匹配背包即可。

分组背包

例题:luogu P1757 通天之分组背包

  • 题意:

\(n\) 件物品和一个大小为 \(m\) 的背包,第 \(i\) 个物品的价值为 \(w_i\),体积为 \(v_i\)每个物品属于一个组,同组内最多只能选择一个物品。求背包能装载物品的最大总价值。

  • 思路:

就是对于每组求一次 0-1 背包即可。

for (int k = 1; k <= ts; k++) {
  for (int i = m; i >= 0; i--) {
    for (int j = 1; j <= cnt[k]; j++) {
      if (i >= w[t[k][j]]) {
        dp[i] = max(dp[i], dp[i - w[t[k][j]]] + c[t[k][j]]);
	  }
	}
  }
}

区间 DP

顾名思义,就是在区间上进行 DP。

DP 顺序为从区间长度小的转移到大的上来。所以我们可以枚举一个断点 \(k\),然后枚举两个端点 \(i,j\)\(j\)\(i+k-1\)。然后我们就在这个区间 \([i,j]\) 做操作。

例题:luogu P1063 能量项链

  • 题意:

有一个环,每次可以选择相邻两个合并,如果前一颗能量珠的头标记为 \(m\),尾标记为 \(r\),后一颗能量珠的头标记为 \(r\),尾标记为 \(n\),则聚合后释放的能量为 \(m\times r\times n\),头标为 \(m\),尾标为 \(n\)。求最大代价。

  • 思路:

显然破环为链。

显然 \(f_{i,j}=\max\{f_{i,j}, f{i,k}+f_{k+1,j}+a_i \times a_{k+1} \times a_{j+1}\}\)

就是由区间 \([i,k]\) 和区间 \([k+1,j]\) 的最大值相加得到的。然后还要加上珠子合并所产生的代价。

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 1e2 + 100;

int n, a[MAXN], dp[MAXN][MAXN], ans;

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    a[i + n] = a[i];
  }
  for (int l = 2; l <= n; l++) {
    for (int i = 1; i + l - 1 <= n << 1; i++) {
      int j = i + l - 1;
      for (int k = i; k < j; k++) {
        ans = max(ans, dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + a[i] * a[k + 1] * a[j + 1]));
      }
    }
  }
  cout << ans;
  return 0;
}