把某周做过的题稍微整理了一下,主要是DP,方便随时复习,也方便后人踩坑。
1 完全背包 · 洛谷 P1616 疯狂的采药 思路 把「时间」看成背包容量,「价值」就是药草收益。每种药草可以无限次采集 ⇒ 完全背包。
状态转移
1 dp[j] = max (dp[j], dp[j - h[i]] + w[i])
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> using namespace std;const int N = 1e4 + 10 ;typedef long long ll;int h[N], w[N];const int M = 1e7 + 10 ;ll dp[M]; int main () { int t, m; scanf ("%d%d" , &t, &m); for (int i = 1 ; i <= m; i++) { scanf ("%d%d" , &h[i], &w[i]); } for (int i = 1 ; i <= m; i++) { for (int j = h[i]; j <= t; j++) { dp[j] = max (dp[j - h[i]] + w[i], dp[j]); } } printf ("%lld\n" , dp[t]); return 0 ; }
2 混合背包 · 洛谷 P1833 樱花 思路 题目同时出现 01、多重、完全三种背包。
p[i] == 0
→ 完全背包
p[i] > 0
→ 01 / 多重背包
状态转移 完全背包:正序 多重背包:拆成 01 后逆序
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <bits/stdc++.h> using namespace std;const int N = 1e4 + 10 ;int t[N], w[N], p[N];int dp[1008 ];int main () { int h1, h2, m1, m2; scanf ("%d:%d %d:%d" , &h1, &m1, &h2, &m2); int n; scanf ("%d" , &n); int ans = (m2 - m1) + (h2 - h1) * 60 ; for (int i = 1 ; i <= n; i++) { scanf ("%d%d%d" , &t[i], &w[i], &p[i]); } for (int i = 1 ; i <= n; i++) { if (p[i] == 0 ) { for (int j = t[i]; j <= ans; j++) { dp[j] = max (dp[j - t[i]] + w[i], dp[j]); } } else { for (int k = 1 ; k <= p[i]; k++) { for (int j = ans; j >= k * t[i]; j--) { dp[j] = max (dp[j - t[i]] + w[i], dp[j]); } } } } printf ("%d\n" , dp[ans]); return 0 ; }
3 二维背包 · 洛谷 P1855 榨取kkksc03 思路 背包有两个限制维度:时间 & 金钱 ⇒ 二维 01 背包。
状态转移
1 dp[j][k] = max (dp[j][k], dp[j - a[i]][k - b[i]] + 1 )
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> using namespace std;int a[108 ], b[108 ];int dp[300 ][300 ];int main () { int n, m, t; scanf ("%d%d%d" , &n, &m, &t); for (int i = 1 ; i <= n; i++) { scanf ("%d%d" , &a[i], &b[i]); } for (int i = 1 ; i <= n; i++) { for (int j = m; j >= a[i]; j--) { for (int k = t; k >= b[i]; k--) { dp[j][k] = max (dp[j][k], dp[j - a[i]][k - b[i]] + 1 ); } } } printf ("%d\n" , dp[m][t]); return 0 ; }
4 分组背包 · 洛谷 P1757 通天之分组背包 思路 物品分多组,每组最多选一个 ⇒ 经典分组背包。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <bits/stdc++.h> using namespace std;const int N = 1e4 + 10 ;int cnt[N];int dp[N];int a[N], b[N];int vis[N][N];int main () { int m, n; scanf ("%d%d" , &m, &n); int ans; int maxn = 0 ; for (int i = 1 ; i <= n; i++) { scanf ("%d%d%d" , &a[i], &b[i], &ans); ++cnt[ans]; vis[ans][cnt[ans]] = i; maxn = max (maxn, ans); } for (int i = 1 ; i <= maxn; i++) { for (int j = m; j >= 0 ; j--) { for (int k = 1 ; k <= cnt[i]; k++) { if (j >= a[vis[i][k]]) { dp[j] = max (dp[j], dp[j - a[vis[i][k]]] + b[vis[i][k]]); } } } } printf ("%d\n" , dp[m]); return 0 ; }
5 带附件的 01 背包 · 洛谷 P1064 [NOIP2006 提高组] 金明的预算方案 思路 每个主件最多带 2 个附件,决策从 2 种变成 5 种: 不选、只选主件、主件 + 附件1、主件 + 附件2、主件 + 全部附件。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <bits/stdc++.h> using namespace std;const int N = 4e4 + 10 ;int dpmainv[N], dpmainw[N];int dpannexv[N][3 ], dpannexw[N][3 ];int cnt[N];int dp[N];int main () { int n, m; scanf ("%d%d" , &n, &m); int a, b, q; for (int i = 1 ; i <= m; i++) { scanf ("%d%d%d" , &a, &b, &q); if (q == 0 ) { dpmainv[i] = a; dpmainw[i] = a * b; } else { cnt[q]++; dpannexv[q][cnt[q]] = a; dpannexw[q][cnt[q]] = a * b; } } for (int i = 1 ; i <= m; i++) { for (int j = n; j >= dpmainv[i] && dpmainv[i] > 0 ; j--) { dp[j] = max (dp[j], dp[j - dpmainv[i]] + dpmainw[i]); if (j >= dpmainv[i] + dpannexv[i][1 ]) { dp[j] = max (dp[j], dp[j - dpmainv[i] - dpannexv[i][1 ]] + dpmainw[i] + dpannexw[i][1 ]); } if (j >= dpmainv[i] + dpannexv[i][2 ]) { dp[j] = max (dp[j], dp[j - dpmainv[i] - dpannexv[i][2 ]] + dpmainw[i] + dpannexw[i][2 ]); } if (j >= dpmainv[i] + dpannexv[i][1 ] + dpannexv[i][2 ]) { dp[j] = max (dp[j], dp[j - dpmainv[i] - dpannexv[i][1 ] - dpannexv[i][2 ]] + dpmainw[i] + dpannexw[i][1 ] + dpannexw[i][2 ]); } } } printf ("%d\n" , dp[n]); return 0 ; }
6 树形 DP · 洛谷 P1352 没有上司的舞会 思路 树上做 01 背包:
dp[u][0]
不参加,儿子可选可不选
dp[u][1]
参加,儿子不能参加
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;const int N = 6e3 + 10 ;int a[N];int dp[N][2 ];int vis[N];vector<int > v[N]; void dfs (int ans) { dp[ans][1 ] = a[ans]; for (int i = 0 ; i < v[ans].size (); i++) { int cnt = v[ans][i]; dfs (cnt); dp[ans][0 ] += max (dp[cnt][0 ], dp[cnt][1 ]); dp[ans][1 ] += dp[cnt][0 ]; } } int main () { int n; scanf ("%d" , &n); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); } int p, q; for (int i = 1 ; i <= n - 1 ; i++) { scanf ("%d%d" , &p, &q); v[q].push_back (p); vis[p] = 1 ; } int ans = 0 ; for (int i = 1 ; i <= n; i++) { if (vis[i] == 0 ) { ans = i; break ; } } dfs (ans); printf ("%d\n" , max (dp[ans][0 ], dp[ans][1 ])); return 0 ; }
7 区间 DP · 能量项链 思路 环形合并石子模板:枚举长度 → 枚举左端点 → 枚举断点。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <cstdio> #include <cstring> #include <math.h> #include <algorithm> #include <vector> using namespace std;const int N = 2e2 + 10 ;int a[N];int dp[N][N];int main () { int n; scanf ("%d" , &n); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); a[i + n] = a[i]; } for (int len = 3 ; len <= n + 1 ; len++) { for (int l = 1 ; l + len - 1 <= 2 * n; l++) { int r = l + len - 1 ; for (int k = l + 1 ; k < r; k++) { dp[l][r] = max (dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]); } } } int maxn = 0 ; for (int i = 1 ; i <= n; i++) { maxn = max (maxn, dp[i][n + i]); } printf ("%d\n" , maxn); return 0 ; }
8 第 k 优解 · HDU 2639 思路 在 01 背包基础上多开一维 dp[j][k]
记录体积为 j 时的前 k 大值。
核心技巧 每次用归并排序思想把两条链(选/不选)的前 k 大合并。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std;const int N = 1e3 + 10 ;int dp[N][N];int a[108 ], b[108 ];int c[108 ], d[108 ];int main () { int t; scanf ("%d" , &t); while (t--) { memset (dp, 0 , sizeof (dp)); memset (c, 0 , sizeof (c)); memset (d, 0 , sizeof (d)); int n, v, p; scanf ("%d%d%d" , &n, &v, &p); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); } for (int i = 1 ; i <= n; i++) { scanf ("%d" , &b[i]); } for (int i = 1 ; i <= n; i++) { for (int j = v; j >= b[i]; j--) { for (int k = 1 ; k <= p; k++) { c[k] = dp[j][k]; d[k] = dp[j - b[i]][k] + a[i]; } int q = 1 , l = 1 , r = 1 ; while (q <= p && (l <= p || r <= p)) { if (c[l] >= d[r]) { dp[j][q] = c[l++]; } else { dp[j][q] = d[r++]; } if (dp[j][q] != dp[j][q - 1 ]) { q++; } } } } printf ("%d\n" , dp[v][p]); } return 0 ; }
9 线段树/搜索 · HDU 5323 思路 根据父子区间关系做 DFS,剪枝:右端点大于当前答案或左端点越界时返回。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <bits/stdc++.h> using namespace std;typedef long long ll;ll ans; void dfs (ll l, ll r) { if (l == 0 ) { if (r != 0 ) { ans = min (ans, r); } } if (r >= ans) { return ; } if (l - 1 - (r - l) < 0 ) { return ; } dfs (l - 1 - (r - l), r); dfs (l, r + 1 + r - l); dfs (l - 1 - (r - l) - 1 , r); dfs (l, r + 1 + r - l - 1 ); } int main () { ll l, r; while (scanf ("%lld%lld" , &l, &r) != EOF) { ans = 0x3f3f3f3f ; dfs (l, r); if (l == 0 && r == 0 ) { printf ("0\n" ); } else { if (ans == 0x3f3f3f3f ) { printf ("-1\n" ); } else { printf ("%lld\n" , ans); } } } return 0 ; }