二维数字三角形模型 🔺
最长上升自序列模型 📈
背包问题 🎒
动态规划
状态表示(思考维度 如二维f[i][j])
集合:f[i][j] 表示哪些情况
属性:f[i][j] 表示集合里的 MAX/MIN/数量
状态计算
划分集合(不重不漏)
状态转移方程
二维数字三角形模型
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 #include <iostream> #include <algorithm> using namespace std;const int Row = 110 ;const int Col = 110 ;int T;int dp[Row][Col];int map[Row][Col];int main () { cin >> T; while (T--) { int r,c; cin >> r >> c; for (int i = 0 ; i < r; i++) { for (int j = 0 ; j < c; j++) { cin >> map[i][j]; } } dp[0 ][0 ] = map[0 ][0 ]; for (int i = 1 ; i < c; i++) dp[0 ][i] = dp[0 ][i - 1 ] + map[0 ][i]; for (int i = 1 ; i < r; i++) dp[i][0 ] = dp[i - 1 ][0 ] + map[i][0 ]; for (int i = 1 ; i < r; i++) { for (int j = 1 ; j < c; j++) { dp[i][j] = max (dp[i - 1 ][j], dp[i][j - 1 ]) + map[i][j]; } } cout << dp[r - 1 ][c - 1 ] << endl; } }
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 #include <iostream> using namespace std;const int N = 15 ;int n;int dp[N][N][N][N];int map[N][N];int main () { cin >> n; int r,c,w; while (cin >> r >> c >> w, r || c || w) { map[r][c] = w; } for (int i1 = 1 ; i1 <= n; i1++) { for (int i2 = 1 ; i2 <= n; i2++) { for (int j1 = 1 ; j1 <= n; j1++) { for (int j2 = 1 ; j2 <= n; j2++) { if (i1 + j1 != i2 + j2) continue ; int t = map[i1][j1]; if (i1 != i2) t += map[i2][j2]; int &x= dp[i1][j1][i2][j2]; x = max (x, dp[i1 - 1 ][j1][i2 - 1 ][j2] + t); x = max (x, dp[i1][j1 - 1 ][i2 - 1 ][j2] + t); x = max (x, dp[i1 - 1 ][j1][i2][j2 - 1 ] + t); x = max (x, dp[i1][j1 - 1 ][i2][j2 - 1 ] + t); } } } } cout << dp[n][n][n][n] << endl; }
最长上升子序列模型
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 #include <iostream> using namespace std;const int N = 1010 ;int a[N];int dp[N]; int main () { int n; cin >> n; for (int i = 1 ; i <= n; i++) { cin >> a[i]; } for (int i = 1 ; i <= n; i++) dp[i] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j < i; j++) { if (a[i] > a[j]) dp[i] = max (dp[i], dp[j] + 1 ); } } int res = 0 ; for (int i = 1 ; i <= n; i++) { res = max (res, dp[i]); } cout << res << endl; }
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 #include <iostream> using namespace std;const int N = 1010 ;int a[N];int up[N];int down[N];int n;int main () { cin >> n; for (int i = 1 ; i <= n; i++) cin >> a[i]; for (int i = 1 ; i <= n; i++) { up[i] = 1 ; for (int j = 1 ; j < i; j++) { if (a[i] > a[j]) up[i] = max (up[i], up[j] + 1 ); } } for (int i = n; i; i--) { down[i] = 1 ; for (int j = n; j > i; j--) { if (a[i] > a[j]) down[i] = max (down[i], down[j] + 1 ); } } int res = 0 ; for (int i = 1 ; i <= n; i++) { res = max (res, up[i] + down[i] - 1 ); } cout << res << endl; }
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 <iostream> using namespace std;const int N = 1010 ;int q[N];int dp[N], g[N];int n;int main () { while (cin >> q[n]) n++; int res = 0 ; for (int i = n - 1 ; i >= 0 ; i --) { dp[i] = 1 ; for (int j = n - 1 ; j > i; j --) { if (q[j] <= q[i]) dp[i] = max (dp[i], dp[j] + 1 ); } } for (int i = 0 ; i < n; i++) res = max (res, dp[i]); cout << res << endl; int cnt = 0 ; for (int i = 0 ; i < n; i++) { int k = 0 ; while (k < cnt && q[i] > g[k]) k++; g[k] = q[i]; if (k >= cnt) cnt++; } cout << cnt << endl; }
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 #include <iostream> using namespace std;const int N = 55 ;int up[N], down[N];int a[N];int ans;int n;void dfs (int depth, int u, int d) { if (u + d >= ans) return ; if (depth == n) { ans = u + d; return ; } int k = 0 ; while (k < u && a[depth] > up[k]) k++; int tmp = up[k]; up[k] = a[depth]; if (k == u) dfs (depth + 1 , u + 1 , d); else dfs (depth + 1 , u, d); up[k] = tmp; k = 0 ; while (k < d && a[depth] < down[k]) k++; tmp = down[k]; down[k] = a[depth]; if (k == d) dfs (depth + 1 , u, d + 1 ); else dfs (depth + 1 , u, d); down[k] = tmp; return ; }int main () { while (cin >> n, n) { for (int i = 0 ; i < n; i++) cin >> a[i]; ans = n; dfs (0 , 0 , 0 ); cout << ans << endl; } }
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 <iostream> using namespace std;const int N = 3010 ;int a[N];int b[N];int dp[N][N];int n;int ans;int main () { cin >> n; for (int i = 1 ; i <= n; i++) cin >> a[i]; for (int i = 1 ; i <= n; i++) cin >> b[i]; for (int i = 1 ; i <= n; i++) { int maxv = 0 ; for (int j = 1 ; j <= n; j++) { dp[i][j] = dp[i - 1 ][j]; if (a[i] == b[j]) dp[i][j] = max (dp[i][j], maxv + 1 ); if (a[i] > b[j]) maxv = max (maxv, dp[i - 1 ][j]); } } for (int i = 1 ; i <= n; i++) ans = max (ans, dp[n][i]); cout << ans << endl; }
背包
多重背包
多重背包 + 二进制优化
关键: 将 个物品分为了 堆物品
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 #include <iostream> using namespace std;const int N = 12010 , M = 2010 ;int n, m;int v[N], w[N]; int f[M]; int main () { cin >> n >> m; int cnt = 0 ; for (int i = 1 ;i <= n;i ++) { int a,b,s; cin >> a >> b >> s; int k = 1 ; while (k<=s) { cnt ++ ; v[cnt] = a * k ; w[cnt] = b * k; s -= k; k *= 2 ; } if (s>0 ) { cnt ++ ; v[cnt] = a*s; w[cnt] = b*s; } } n = cnt ; for (int i = 1 ;i <= n ;i ++) for (int j = m ;j >= v[i];j --) f[j] = max (f[j],f[j-v[i]] + w[i]); cout << f[m] << endl; return 0 ; }
多重背包 + 单调队列 + 一维数组
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 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 1010 ;const int M = 20010 ;int dp[2 ][M];int v[N], w[N], s[N];int q[M];int n, m;int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) { cin >> v[i] >> w[i] >> s[i]; } for (int i = 1 ; i <= n; i++) { for (int r = 0 ; r < v[i]; r++) { int hh = 0 , tt = -1 ; for (int j = r; j <= m; j += v[i]) { while (hh <= tt && q[hh] < j - s[i] * v[i]) hh ++; while (hh <= tt && dp[(i - 1 ) & 1 ][q[tt]] + (m - q[tt]) / v[i] * w[i] <= dp[(i - 1 ) & 1 ][j] + (m - j) / v[i] * w[i]) -- tt; q[++ tt] = j; dp[i & 1 ][j] = dp[(i - 1 ) & 1 ][q[hh]] + (j - q[hh]) / v[i] * w[i]; } } } cout << dp[n & 1 ][m] << endl; return 0 ; }
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 #include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int n, m;int w[N], v[N];int dp[N][N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> v[i] >> w[i]; for (int i = n; i >= 1 ; i--) { for (int j = 0 ; j <= m; j++) { dp[i][j] = dp[i + 1 ][j]; if (j >= v[i]) dp[i][j] = max (dp[i][j], dp[i + 1 ][j - v[i]] + w[i]); } } int j = m; for (int i = 1 ; i <= n; i++) { if (j >= v[i] && dp[i][j] == dp[i + 1 ][j - v[i]] + w[i]){ cout << i << " " ; j -= v[i]; } } return 0 ; }
分组背包
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 #include <bits/stdc++.h> using namespace std;const int N = 110 ;int f[N];int v[N][N],w[N][N],s[N];int n, m, k;int main () { cin >> n >> m; for (int i = 0 ; i < n; i++){ cin >> s[i]; for (int j = 0 ; j < s[i]; j++){ cin >> v[i][j] >> w[i][j]; } } for (int i = 0 ; i < n; i++){ for (int j = m; j >= 0 ; j--){ for (int k = 0 ; k < s[i]; k++){ if (j >= v[i][k]) f[j] = max (f[j],f[j-v[i][k]]+w[i][k]); } } } cout << f[m] << endl; }
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 #include <cstring> #include <iostream> #include <algorithm> #include <vector> #define v first #define w second using namespace std;typedef pair<int , int > PII;const int N = 60 , M = 32010 ;int n, m; PII master[N]; vector<PII> servent[N];int f[M];int main () { cin >> m >> n; for (int i = 1 ; i <= n; i ++ ) { int v, p, q; cin >> v >> p >> q; p *= v; if (!q) master[i] = {v, p}; else servent[q].push_back ({v, p}); } for (int i = 1 ; i <= n; i ++ ) for (int u = m; u >= 0 ; u -- ) { for (int j = 0 ; j < 1 << servent[i].size (); j ++ ) { int v = master[i].v, w = master[i].w; for (int k = 0 ; k < servent[i].size (); k ++ ) if (j >> k & 1 ) { v += servent[i][k].v; w += servent[i][k].w; } if (u >= v) f[u] = max (f[u], f[u - v] + w); } } cout << f[m] << endl; return 0 ; }
树形 + 分组背包 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 110 ;const int V = 110 ;int n, m;int dp[N][V], e[N], ne[N], h[N];int v[N], w[N];int root;int idx;void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; }void dfs (int x) { for (int i = h[x]; i != -1 ; i = ne[i]) { int u = e[i]; dfs (u); for (int j = m - v[x]; j >= 0 ; j--) { for (int k = 0 ; k <= j; k++) { dp[x][j] = max (dp[x][j], dp[x][j - k] + dp[u][k]); } } } for (int j = m; j >= v[x]; j--) dp[x][j] = dp[x][j - v[x]] + w[x]; for (int j = 0 ; j < v[x]; j++) dp[x][j] = 0 ; }int main () { memset (h, -1 , sizeof (h)); cin >> n >> m; for (int i = 1 ; i <= n; i++) { int p; cin >> v[i] >> w[i] >> p; if (p == -1 ) root = i; else add (p, i); } dfs (root); cout << dp[root][m] << endl; return 0 ; }
完全背包(选法)
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 #include <iostream> #include <algorithm> using namespace std;const int N = 16 ; const int M = 3010 ; int a[] = {0 , 10 , 20 , 50 , 100 }; int m;int dp[N][M]; int main () { cin >> m; int n = 4 ; dp[0 ][0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= m; j++) { dp[i][j] = dp[i - 1 ][j]; for (int k = 1 ; k * a[i] <= j; k++) { dp[i][j] += dp[i - 1 ][j - k * a[i]]; } } } cout << dp[n][m] << endl; return 0 ; }
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 #include <iostream> #include <algorithm> using namespace std;const int N = 16 ; const int M = 3010 ; int a[] = {0 , 10 , 20 , 50 , 100 }; int m;int dp[M]; int main () { cin >> m; int n = 4 ; dp[0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = m; j >= 0 ; j--) { for (int k = 1 ; k * a[i] <= j; k++) { dp[j] += dp[j - k * a[i]]; } } } cout << dp[m] << endl; return 0 ; }
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 #include <iostream> #include <algorithm> using namespace std;const int N = 16 ; const int M = 3010 ; int a[N]; int n, m;long long dp[N][M]; int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++ ) cin >> a[i]; dp[0 ][0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= m; j++) { for (int k = 0 ; k * a[i] <= j; k++) { dp[i][j] += dp[i - 1 ][j - k * a[i]]; } } } cout << dp[n][m] << endl; }
一维优化:
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 #include <iostream> #include <algorithm> using namespace std;const int N = 16 ; const int M = 3010 ; int a[N]; int n, m;long long dp[M]; int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++ ) cin >> a[i]; dp[0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = m; j >= 0 ; j--) { for (int k = 1 ; k * a[i] <= j; k++) { dp[j] += dp[j - k * a[i]]; } } } cout << dp[m] << endl; }
时间优化:
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 #include <iostream> #include <algorithm> using namespace std;const int N = 16 ; const int M = 3010 ; int a[N]; int n, m;long long dp[M]; int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++ ) cin >> a[i]; dp[0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = a[i]; j <= m; j++) { dp[j] += dp[j - a[i]]; } } cout << dp[m] << endl; }
中等难度
本题的关键在于要观察到这是线性代数的极大无关组表示(用b[i]表示):
其一,a,b之间可以相互表示
其二,b中的数字一定取自a
其三,b是表示出 a中最优的(b数组元素间不能相互表示)
关于第二点的证明:
反证法:假设b存在一个不等于a中任一元素的元素,不妨设为 ,由于b必须能表示出a,有:
第二个等号成立在于a也可以由b表示且由于其他数字(除了b_i的其他数)都取自a,因此可以用除了 的数字来表示,这与第三点最优性矛盾。
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 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 110 ;const int M = 25010 ;int n;int a[N];int dp[M];int T;int main () { cin >> T; while (T --) { cin >> n; int ans = n; for (int i = 1 ; i <= n; i++) cin >> a[i]; sort (a + 1 , a + n + 1 ); dp[0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = a[i]; j <= a[n]; j++) { dp[j] += dp[j - a[i]]; } } int k = 1 ; for (int i = a[1 ]; i <= a[n]; i++) { if (i == a[k]) { k ++; if (dp[i] > 1 ) ans --; } } memset (dp, 0 , sizeof (dp)); cout << ans << endl; } return 0 ; }
混合背包(0-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 47 #include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;const int M = 1010 ;int n, m;int dp[M];int main () { cin >> n >> m; for (int i = 0 ; i < n; i++) { int v, w, s; cin >> v >> w >> s; if (s == 0 ) { for (int j = v; j <= m; j++) dp[j] = max (dp[j], dp[j - v] + w); } else { if (s == -1 ) s = 1 ; for (int k = 1 ; k <= s; k *= 2 ) { for (int j = m; j >= k * v; j --) { dp[j] = max (dp[j], dp[j - k * v] + k * w); } s -= k; } if (s) { for (int j = m; j >= s * v; j --) { dp[j] = max (dp[j], dp[j - s * v] + s * w); } } } } cout << dp[m]; return 0 ; }
背包最优方案数量
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 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int mod = 1e9 +7 ;const int N = 1010 ;const int V = 1010 ;int dp[V];int g[V];int n, m;int v[N], w[N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> v[i] >> w[i]; g[0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = m; j >= v[i]; j--) { int maxv = max (dp[j], dp[j - v[i]] + w[i]); int cnt = 0 ; if (maxv == dp[j - v[i]] + w[i]) cnt += g[j - v[i]]; if (maxv == dp[j]) cnt += g[j]; g[j] = cnt % mod; dp[j] = maxv; } } int res = 0 ; for (int j = 0 ; j <= m; j++) { res = max (res, dp[j]); } int cnt = 0 ; for (int j = 0 ; j <= m; j++) { if (dp[j] == res) cnt = (cnt + g[j]) % mod; } cout << cnt << endl; return 0 ; }
贪心推公式 + DP
最重要的部分在于说要推出公式:
考虑两个相邻的不会耗为 0 的能量石(i, i + 1):
先吃 i,其能量为:
先吃 i+1,其能量为:
可以发现前两项相等,只有最后一项不能,当 时先吃i更好
移项后即为
那么可以证明的是:当所有能量石按照 从小到大排序才可能得到最优解(最优解至少一定是按此顺序)
证明:若不是从小到大排序,那么考虑其中一对相邻的(m,m+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 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 100010 ;int n;struct Stone { int S; int E; int L; bool operator < (const Stone &W) const { return S * W.L < L * W.S; } }stone[N];int dp[N]; int main () { int T; cin >> T; for (int t = 1 ; t <= T; t++) { cin >> n; int m = 0 ; for (int i = 0 ; i < n; i++) { cin >> stone[i].S >> stone[i].E >> stone[i].L; m += stone[i].S; } sort (stone, stone + n); memset (dp, -0x3f , sizeof (dp)); dp[0 ] = 0 ; for (int i = 0 ; i < n; i++) { int s = stone[i].S, l = stone[i].L, e = stone[i].E; for (int j = m; j >= s; j--) dp[j] = max (dp[j], dp[j - s] + e - (j - s) * l); } int res = 0 ; for (int j = 0 ; j <= m; j++) res = max (res, dp[j]); cout << "Case #" << t << ": " << res << endl; } return 0 ; }