动态规划算法
1. 背包问题
1.1 01背包问题
约束条件是:给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。
题目链接
典型的01背包问题。骨头的数量已知,接下来又知道了每个骨头的价值和体积,这就相当于每种物品只有一个,对每种物品只需考虑选与不选两种情况。并且,体积的限制是不能超过背包的容积V。很容易想到以下做法:
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 <string.h> using namespace std;
const int maxn = 1005; int w[maxn], v[maxn], dp[maxn][maxn];
int main(){ int T, N, V; cin >> T; while(T--){ cin >> N >> V; int i, j; for(i = 1; i <= N; i++) cin >> w[i]; for(i = 1; i <= N; i++) cin >> v[i]; memset(dp, 0, sizeof(dp)); for(i = 1; i <= N; i++){ for(j = 0; j <= V; j++){ if(j >= v[i]) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]); else dp[i][j] = dp[i - 1][j]; } } cout << dp[N][V] << endl; } return 0; }
|
上述做法的确AC了。但是,这里的N,V最大只有1000,一旦变得更大了,开一个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
|
#include <iostream> #include <string.h> using namespace std;
const int maxn = 1005; int w[maxn], v[maxn], dp[maxn];
int main(){ int T, N, V; cin >> T; while(T--){ cin >> N >> V; int i, j; for(i = 1; i <= N; i++) cin >> w[i]; for(i = 1; i <= N; i++) cin >> v[i]; memset(dp, 0, sizeof(dp)); for(i = 1; i <= N; i++) for(j = V; j >= v[i]; j--) dp[j] = max(dp[j], dp[j - v[i]] + w[i]); cout << dp[V] << endl; } return 0; }
|
1.2 完全背包问题
基本概念:与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
|
#include <iostream> #include <string.h> using namespace std;
const int maxn = 10005; int dp[maxn];
int main(){ int T, P, W, E, F, N; cin >> T; while(T--){ cin >> E >> F; int V = F - E; cin >> N; memset(dp, -1, sizeof(dp)); dp[0] = 0; int i, j; for(i = 1; i <= N; i++){ cin >> P >> W; for(j = 0; j <= V - W; j++){ if(dp[j] == -1) continue; else if(dp[j + W] == -1) dp[j + W] = dp[j] + P; else dp[j + W] = min(dp[j + W], dp[j] + P); } } if(dp[V] == -1) cout << "This is impossible." << endl; else cout << "The minimum amount of money in the piggy-bank is " << dp[V] << "." << endl; } return 0; }
|
1.3 多重背包问题
基本概念:多重背包的特点是物品的数量可以大于1但有限制。状态定义与01背包一致。
题目链接
已知手表价格大于0且不超过M,有N种硬币,已知每种硬币的价值和数量,求能用硬币支付的手表价格的情况有多少种。
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 <iostream> #include <string.h> using namespace std;
int dp[100005], a[105], c[105], w[1005];
int main(){ int n, m; while(cin >> n >> m && (n || m)){ memset(dp, 0, sizeof(dp)); dp[0] = 1; int i, j; for(i = 1; i <= n; i++) cin >> a[i]; for(i = 1; i <= n; i++) cin >> c[i]; int cnt = 0; for(i = 1; i <= n; i++) for(j = 1; c[i] > 0; j *= 2){ int t = min(j, c[i]); c[i] -= t; w[cnt++] = a[i]*t; } int ans = 0; for(i = 0; i < cnt; i++) for(j = m; j >= w[i]; j--) if(dp[j-w[i]]) dp[j] = 1; for(i = 1; i <= m; i++) ans += dp[i]; cout << ans << endl; } return 0; }
|
2. 状态压缩
2.1 旅行商问题
题目链接
访问N座城市,共有M条路,必须访问所有的城市至少一次,并且访问次数不能超过两次(也就是1次或2次)。已知每条道路的费用,求最小费用,若不能完成就返回-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
| #include <iostream> #include <cstdio> #include <string.h> #include <algorithm> using namespace std;
int dp[60000][11], c[20][20], len[11]; #define Gets(i, j) (i/len[j])%3
int main(){ len[0] = 1; for(int i = 1; i <= 10; i++) len[i] = len[i-1] * 3; int n, m, f, a, b, w, u, v, ans; while(~scanf("%d%d", &n, &m)){ memset(c, 0x3f, sizeof(c)); memset(dp, 0x3f, sizeof(dp)); for(int i = 0; i < m; i++){ scanf("%d%d%d", &a, &b, &w); a--; b--; c[a][b] = c[b][a] = min(c[a][b], w); } ans = dp[0][0]; int i, j; for(i = 0; i < n; i++) dp[len[i]][i] = 0; for(i = 1; i < len[n]; i++){ f = 1; for(j = 0; j < n; j++){ if(Gets(i, j)==0){ f = 0; continue; } for(int v = 0; v < n; v++){ if(Gets(i, v)==2) continue; u = i + len[v]; dp[u][v] = min(dp[u][v], dp[i][j] + c[j][v]); } } if(f){ for(j = 0; j < n; j++) ans = min(ans, dp[i][j]); } } if(dp[0][0]==ans) ans = -1; printf("%d\n", ans); } return 0; }
|
2.2 插头dp
基本概念:插头dp也是状态压缩dp的一种,成为轮廓线dp。通常用于解决二维空间上的状态压缩问题,且约束条件是每个位置只需要关心与自己邻近的几个点。所以,应该按顺序逐个位置进行处理。
题目链接
3. 动态规划优化
3.1 数据结构优化
3.2 斜率优化
3.3 四边形不等式优化
4. 常见动态规划题目类型
4.1 树形dp
4.2 RMQ问题
4.3 有向图最短路
4.4 最长上升子序列
5. 习题解析的博客链接