【洛谷】训练场_动态规划的背包问题篇(不全)

P1060开心的金明

题意:

金明有n块钱,他有m样东西可买,这m样东西每份都有自己的价格v与重要度(lv:1~5),求在他不超过n元的前提下,使每件物品的价格与重要度的乘积的总和最大。

分析:

Dp的话要开一个二维数组,n < 30000, m < 25, 这个二维数组还是可以开出来的。

接着就是简单的dp模板题了。

 1 int rec(int i, int j)
 2 {
 3     if(dp[i][j] >= 0) return dp[i][j];  //
 4 
 5     int res;
 6     if(i == m) res = 0;    //
 7     else if(j < buy[i].v) res = rec(i + 1, j);  //
 8     else res = max(rec(i + 1, j), rec(i + 1, j - buy[i].v) + buy[i].v * buy[i].l); //
 9 
10     return dp[i][j] = res;
11 }
模板

这里使用的记忆化搜索以减少搜索时间。

①    :判断是否已经搜过这点,是则直接返回;

②    :已经搜索完物品

扫描二维码关注公众号,回复: 6117821 查看本文章

③    :背包装不下该物品的重量(这里是价值量),则跳过该物品

④    :可以选,比较选与不选哪两者的价值更大,谁大就先记录哪个

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int maxn = 26;
 7 int n, m;
 8 struct node{
 9     int v; // 价值
10     int l; // 等级
11 }buy[maxn];
12 int dp[maxn+1][30005];
13 int ans, res;
14 
15 int rec(int i, int j)
16 {
17     if(dp[i][j] >= 0) return dp[i][j];
18 //    printf("[%d][%d]\n", i, j);
19     int res;
20     if(i == m) res = 0;
21     else if(j < buy[i].v) res = rec(i + 1, j);
22     else res = max(rec(i + 1, j), rec(i + 1, j - buy[i].v) + buy[i].v * buy[i].l);
23 
24     return dp[i][j] = res;
25 }
26 
27 int main()
28 {
29     while(cin >> n >> m){
30         for(int i = 0; i < m; i++) cin >> buy[i].v >> buy[i].l;
31         memset(dp, -1, sizeof(dp));
32 
33         cout << rec(0, n) << endl;
34     }
35     return 0;
36 }
AC代码

下一题:

P1164小A点菜

题意:

Uim与小A去吃饭,这家店的菜式有n种但每种只能选一次,uim有m块钱,保证能把m块钱刚好花完,问有多少种点菜方式。如,n = 4,m = 4,n道菜分别1,1,2,2(元),即有3种选择[1,1,2(3)][1,1,2(4)][2,2]。

分析:

本题跪了,因为我还领略到dp的精髓,于是看了题解。

现在我可以粗略地认为,dp在一些问题上,就是“选与不选”的关系,然后再判断这关系前后的结果问题。

比如这题:

点菜只有点与不点的关系,并且数据了保证m块一定会被花完,因此就有以下关系:

If(j – 第i道菜的价格) dp[i][j] = dp[i-1][j] + 1; // 钱刚好充足,则点菜的方案+1

这个是钱充足的特例,当钱不刚好充足的时候,即钱比菜钱多的时候有以下关系:

If(i-第i道菜的价格)dp[i][j] = dp[i-1][j] + f[I-1][j-v[i]]; // 吃这道菜之前的方案数加上吃这道菜的方案数

如果钱不充足的话,就不吃(白给),与吃第i-1道菜的方案数相同:

If(i-第i道菜的价格)dp[i][j] = dp[i-1][j];

最后输出dp[n][m]即可得到AC,因为以上遍历的意思就是:有m钱,对于吃这n道菜的方案数,所以,dp[n][m]就是最终答案。

感谢洛谷作者 sslzgrh 的题解分析。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 int n, m;
 7 int dp[102][10003];
 8 int v[102];
 9 
10 int main()
11 {
12     while(cin >> n >> m){
13         for(int i = 1; i <= n; i++) cin >> v[i];
14 
15         for(int i = 1; i <= n; i++){
16             for(int j = 1; j <= m; j++){
17                 if(j == v[i]) dp[i][j] = dp[i-1][j] + 1;
18                 if(j > v[i]) dp[i][j] = dp[i-1][j] + dp[i-1][j-v[i]];
19                 if(j < v[i]) dp[i][j] = dp[i-1][j];
20             }
21         }
22         cout << dp[n][m] << endl;
23     }
24     return 0;
25 }
AC代码

先暂停下,这时我开始对dp有点头绪了:dp也是搜索的一种,只不过这种搜索未必能把所有情况搜索完毕。解题的关键是找出“是”与“非”二者之间的关系所带来的关系,以及前一种关系与后一种关系之间的关系(这么说是不是已经晕了)。我现在困惑的是,如何才能构造出与表达这种关系相适应的算法?也许这只能多做题了吧。

下一题:

P1048采药

题意:

辰辰到山里采药,他有T个小时,山里有M株草药,每株草药都有自己所需要花费的时间t与价值v,问在T小时辰辰采到的药草的最大价值总和。

分析:

?????这不就是完全的01背包问题了吗?比开心的金明还要01背包问题,即关系只有采与不采的关系,完全的水题。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 int T, M;
 7 int t[102];
 8 int v[102];
 9 int dp[102][1002]; // 默认每株草药花费的时间在1~1000
10 
11 int main()
12 {
13     while(cin >> T >> M){
14         for(int i = 0; i < M; i++) cin >> t[i] >> v[i];
15 
16         for(int i = 0; i < M; i++){
17             for(int j = 0; j <= T; j++){
18                 if(j < t[i])
19                     dp[i+1][j] = dp[i][j];
20                 else
21                     dp[i+1][j] = max(dp[i][j], dp[i][j-t[i]] + v[i]);
22             }
23         }
24         cout << dp[M][T] << endl;
25     }
26     return 0;
27 }
AC代码

下一题:

P1049装箱问题

题意:

有个容量为V的箱子,并且有n个物品,每个物品有属于自己的体积v,问最后装得的箱子的容量最小。

分析:

同样也是01背包问题,只不过最后要还要用总量减去最大值。

V – dp[n][V]

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 int V, n;
 7 int bag[32];
 8 int dp[32][20006];
 9 
10 int main()
11 {
12     while(cin >> V >> n){
13         for(int i = 0; i < n; i++) cin >> bag[i];
14 
15         for(int i = 0; i < n; i++){
16             for(int j = 0; j <= V; j++){
17                 if(j < bag[i])
18                     dp[i+1][j] = dp[i][j];
19                 else
20                     dp[i+1][j] = max(dp[i][j], dp[i][j-bag[i]] + bag[i]);
21             }
22         }
23         cout << V - dp[n][V] << endl;
24     }
25     return 0;
26 }
AC代码

下一题:

T1616疯狂的采药

题意:

采药一题题意几乎一样,只不过草药可以无限采。

分析:

可以无限采的话就变成了完全背包问题,如果数据小的话,这样写就可以AC了

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 
 5 int T, M;
 6 int t[10005], v[10005];
 7 int dp[100005]
 8 int main()
 9 {
10     while(cin >> T >> M){
11         for(int i = 0; i < M; i++) cin >> t[i] >> v[i];
12 
13         for(int i = 0; i < M; i++){
14             for(int j = 0; j <= T; j++){
15                 if(j < t[i])
16                     dp[i+1][j] = dp[i][j];
17                 else
18                     dp[i+1][j] = max(dp[i][j], dp[i+1][j-t[i]] + v[i]);
19             }
20         }
21         cout << dp[M][T] << endl;
22     }
23     return 0;
24 }
完全背包问题模板

但该题数据大的让人恶心,开二维数组是一定会爆的,所以得转换一下(分析贴在代码里):

感谢洛谷作者神云_cloud的题解:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 
 5 int T, M;
 6 int t[10005], v[10005];
 7 int dp[100005];
 8 int main()
 9 {
10     while(cin >> T >> M){
11         for(int i = 0; i < M; i++) cin >> t[i] >> v[i];
12 
13         for(int i = 0; i < M; i++){
14             for(int j = t[i]; j <= T; j++){ // 把原来的 j = 0 改成 j = t[i]
15 //                if(j < t[i])
16 //                    dp[i+1][j] = dp[i][j];
17 //                else                          // 这样一来就默认 j <= T 了
18                     dp[j] = max(dp[j], dp[j-t[i]] + v[i]);
19             }
20         }
21         cout << dp[T] << endl;
22     }
23     return 0;
24 }
AC代码

最后小结:

以上题目都是被洛谷标记为普及难度的dp题,包含了01背包问题完全背包问题,通过这几题的练习可以熟悉dp的模板与特性。不过dp相比贪心算法的思路还是更具有图表型,但有时太固执于图表又会陷入数据过大而不知所措的困境(如疯狂的采药这题)。

还是那种感觉,dp算法是更多解决“拿“与”不拿“的问题。但数据如果变大了要怎么办?这得要对dp的思维特别熟悉才能够把dp优化。所以,除了会”默写“,更重要的还是要掌握思想呀。

或许你会觉得我这有些分析写跟没写一样,因为我觉得真的都是模板题或者加一点转变,相信聪明的你熟悉模板的话一看就想到该怎么写了。我是拿着《(第2版)挑战程序设计竞赛》一书边看别学边敲的,所以感觉知道有这样的模板算法,一看题目就知道是水题了quq,着实抱歉。 

谢谢你能看到最后。

猜你喜欢

转载自www.cnblogs.com/Ayanowww/p/10809896.html
今日推荐