DP 重修笔记
DP 重修笔记
背包DP
其实大多的背包问题都是转换成 \(01\) 背包再优化复杂度的。
- \(01\) 背包问题
就是物品只有选和不选两种选择,对应着 \(0, 1\) 关系,所以叫 \(01\) 背包。
我们设 \(f[i][j]\) 表示前 \(i\) 个物品,容量为 \(j\) 的背包最多能装下的价值。
\(i\) 是由 \(i - 1\) 转移来的,对于第 \(i\) 个物品,只有选和不选两种可能,不选的话,就等于 \(f[i - 1][j]\), 选的话,就是由 \(f[i - 1][j - v[i]] + w[i]\) 转移来的。
这样的话空间和时间复杂度为 \(\mathcal{O}(nk)\)。
但是我们仔细观察,第 \(i\) 次抉择只与 \(i - 1\) 次抉择有关系,所以我们可以将第一位滚动掉。
但是我们要保证你用 \(f[j], f[j - v[i]] + w[i]\) 来更新的时候,存的是 \(i - 1\) 时更新的,所以我们要保证在枚举他的时候,\(j - v[i]\) 还未被更新过,所以我们要倒叙枚举他,来保证 \(f[j - v[i]]\) 是 \(i - 1\) 的。
for(int i = 1; i <= n; i++)
for(int j = V; j >= v[i]; j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
- 多维费用背包
就套循环就行了,拿二维举个例子。
for(int i = 1; i <= n; i++) {
for(int j = V; j >= v[i]; j--) {
for(int k = M; k >= m[i]; k--) {
f[j][k] = max(f[j][k], f[j - v[i]][k - m[i]] + w[i]);
}
}
}
- 完全背包问题
当你改变了枚举顺序,\(f[i][j]\) 在 \(v[i] \le j\) 的情况下 \(f[i][j]\) 会被 \(f[i - 1][j - w[i]]\) 多次更新,这相当于将物品 \(i\) 多次放入背包。
for(int i = 1; i <= n; i++)
for(int j = v[i]; j <= V; j++)
f[j] = max(f[j], f[j - v[i]] + w[i]);
- 多重背包问题:
就是每个物品有 \(k_i\) 种。
我们可以把他看成 \(k_i\) 个不同的物品,每个物品可以选一遍。
于是就转换成了 \(01\) 背包问题。
复杂度 \(\mathcal{O}{} (W { \sum_{i=1}^{n}}k_i)\)
这样的复杂度还不是太优。我们考虑优化分组的过程。我们知道一个数一定可以由 \(2\) 的幂次表示。
于是我们用二进制来优化。
int n = read(), V = read();
for(int i = 1 ; i <= n; i++) {
int v = read(), w = read(), k = read();
int c = 1;
while(k - c >= 0) {
k -= c;
a[++idx].v = c * v;
a[idx].w = c * w;
}
a[++idx].v = v * k;
a[idx].w = w * k;
}
这样分完后再来做 \(01\) 背包就行。
复杂度为 \(\mathcal{O}(W \sum_{i=1}^{n} log_2k_i)\)。
线性DP
- 最长公共子序列 和 最大上升子序列
其实是一类问题,这里只讲 \(\mathcal{O}(n \log n)\) 的做法。
我们设 \(dp_i\) 表示长度为 \(i\) 的最长上升子序列的末端元素大小。
显然 \(dp[1] = a[1]\)。
如果 \(a_i > dp[len]\) 显然可以直接接在后面 \(dp[++len] = a[i]\)。
否则就找一个位置将他替换掉,找到第一个比他大的数去调换掉,这样肯定更优。
int n, a[MAXN], b[MAXN];
int rk[MAXN], dp[MAXN], len;
signed main()
{
n = read();
for(int i = 1; i <= n; i++) a[i] = read(), rk[a[i]] = i;
for(int i = 1; i <= n; i++) b[i] = read(), b[i] = rk[b[i]];
dp[1] = b[1], len = 1;
for(int i = 2; i <= n; i++) {
if(b[i] >= dp[len]) dp[++len] = b[i];
else dp[upper_bound(dp + 1, dp + len + 1, b[i]) - dp] = b[i];
}
return print(len), 0;
}
LCS 很简单, LIS 也很简单,那 LCIS 呢?
也很简单。
int n, m, a[N], b[N];
int f[1000][1000], pre[1000][1000];
int ANS[N], cnt = 0;
signed main()
{
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
m = read();
for(int i = 1; i <= m; i++) b[i] = read();
a[0] = b[0] = -INF;
for(int i = 1; i <= n; i++) {
int Max = 0, pos = 0;
for(int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
pre[i][j] = j;
if(a[i] == b[j]) {
if(f[i][j] < Max + 1) f[i][j] = Max + 1, pre[i][j] = pos;
}
if(a[i] > b[j]) {
if(f[i - 1][j] > Max) Max = f[i - 1][j], pos = j;
}
}
}
int ans = 0, wz = 0;
for(int i = 1; i <= m; i++) {
if(f[n][i] > ans) ans = f[n][i], wz = i;
}
printf("%d\n", ans);
int i = n, j = wz;
while(i >= 1 && j >= 1) {
// cout << i << " " << j << "\n";
if(pre[i][j] != j) ANS[++cnt] = b[j];
j = pre[i][j], i--;
}
for(int i = cnt; i >= 1; i--) printf("%d ", ANS[i]);
return 0;
}