2、背包问题和采药一类题的解决方法(C++、递归、状态转移)

 

2 h0173. 01背包问题(输入方式改一下就是一样的题)

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。0<N,V≤1000

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。0<vi,wi≤1000

输出格式:
输出一个整数,表示最大价值。

输入样例:
4 7
1 2
2 4
3 4
4 5
输出样例:
11
代码长度限制

16 KB

时间限制

400 ms

内存限制

64 MB

-----------------------------------------------------------------------------------------------------------------------

        其实本题与之前的数字三角形大同小异。 

方法一:递归 

        最简单的递归方式,缺点是做了很多多余和重复的计算

#include <iostream>
#include <algorithm>
#include <cstdio>
 
using namespace std;
 
int t[1010]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
 
int T; //可以使用的时间
int M; //药材个数
 
//递归方法解决问题
//需要一个时间数组、一个价值数组
//从序号五开始判断
//选择序号的药材,就减去所需时间,并加上药材价值
//不选择就判断下一序号药品
//当前最大时间超过要求时间就不再计算
//找最大价值的路径
int KnapSack(int i, int j);
 
int main()
{
    //输入总时间T和物品总数M
    cin >> T >> M;
    for(int i = 1; i <= M; i++)
    {
        //输入每个药材的时间和价值
        cin >> t[i] >> v[i];
    }
 
    //调用函数解决问题,为了后期优化为滚动数组,故从后往前
    cout << KnapSack(M, T) << endl;
 
    return 0;
 
}
 
//i为药材序号、j为剩下的时间
int KnapSack(int i, int j)
{
    //当序号遍历完说明结束
    //或者所剩时间<=0了,说明来不及了,结束
    if(i == 0 || j <= 0)
        return 0;
    //如果挖完当前药材的时间,比剩下的时间多
    //,说明不能挖这个,看挖下一个行不行,序号变为i-1
    //由于没挖这个药材,所以所剩时间j没有变
    if(t[i] > j)
        return KnapSack(i-1, j);
 
    //当挖完当前药材的时间,小于剩下的时间,说明可以挖也可以不挖
 
    //如果挖,挖完之后看下一个行不行,序号变为i-1
    //由于挖了这个药材,所以剩余时间j-挖这个药材需要的时间t[i]
    //所挖药材的价值 = 当前药材的价值 +
    int x = v[i] + KnapSack(i-1, j-t[i]);
    //如果不挖,剩余时间j不变
    //所挖药材的价值 = 之后判断出来价值最大路径的总价值
    int y = KnapSack(i-1, j);
 
    //最后判断两个路径的值哪个大返回哪个
    return max(x, y);
}
 
 
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/

方法二:递归+备忘录

#include <iostream>
#include <algorithm>
#include <cstdio>
 
using namespace std;
 
int t[110]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
 
int dp[110][1010];//代表取前i种药品,在时间为j时的最大价值***
 
int T; //可以使用的时间
int M; //药材个数
 
//递归方法解决问题
//需要一个时间数组、一个价值数组
//从序号五开始判断
//选择序号的药材,就减去所需时间,并加上药材价值
//不选择就判断下一序号药品
//当前最大时间超过要求时间就不再计算
//找最大价值的路径
int KnapSack(int i, int j);
 
int main()
{
    //输入总时间T和物品总数M
    cin >> T >> M;
    for(int i = 1; i <= M; i++)
    {
        //输入每个药材的时间和价值
        cin >> t[i] >> v[i];
    }
    //备忘录初始化为-1***
    for(int i = 1; i <= M; i++)
    {
        for(int j = 1; j <= T; j++)
        {
            dp[i][j] = -1;
        }
    }
 
    //调用函数解决问题
    cout << KnapSack(M, T) << endl;
 
    return 0;
 
}
 
//i为药材序号、j为剩下的时间
int KnapSack(int i, int j)
{
    //当序号遍历完说明结束
    //或者所剩时间<=0了,说明来不及了,结束
    if(i == 0 || j <= 0)
    {
        return 0;
    }
    //如果挖完当前药材的时间,比剩下的时间多
    //,说明不能挖这个,看挖下一个行不行,序号变为i-1
    //由于没挖这个药材,所以所剩时间j没有变***
    if(dp[i][j] != -1)
        return dp[i][j];
    if(t[i] > j)
    {
        dp[i][j] = KnapSack(i-1, j);
        return dp[i][j];
    }
 
 
    //当挖完当前药材的时间,小于剩下的时间,说明可以挖也可以不挖
 
    //如果挖,挖完之后看下一个行不行,序号变为i-1
    //由于挖了这个药材,所以剩余时间j-挖这个药材需要的时间t[i]
    //所挖药材的价值 = 当前药材的价值 +
    int x = v[i] + KnapSack(i-1, j-t[i]);
    //如果不挖,剩余时间j不变
    //所挖药材的价值 = 之后判断出来价值最大路径的总价值
    int y = KnapSack(i-1, j);
 
    //最后判断两个路径的值哪个大返回哪个***
    dp[i][j] = max(x, y);
    return dp[i][j];
}
 
 
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/

方法三:状态转移+备忘录

        当 当前时间和药品数量都为0时,得到的价值必为0,在开头我们将dp备忘录初始化为0。然后根据状态转移方程,不选的情况就是直接等于上一个的价值:dp[ i ] [ j ] = dp[ i-1 ][ j ],选的情况就是判断左右分支的大小,选大的那个:max(dp[ i-1 ][ j ] ,dp[ i-1 ] [ j-t [ i ] ]+v[ i ]);

#include <iostream>
#include <algorithm>
#include <cstdio>
 
using namespace std;
 
int t[110]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
 
int dp[110][1010] = {0};//代表取前i种药品,在时间为j时的最大价值***
 
int T; //可以使用的时间
int M; //药材个数
 
 
int KnapSack(int i, int j);
 
int main()
{
    //输入总时间T和物品总数M
    cin >> T >> M;
    for(int i = 1; i <= M; i++)
    {
        //输入每个药材的时间和价值
        cin >> t[i] >> v[i];
    }
 
 
    for(int i = 1; i <= M; i++)
    {
        for(int j = T; j >= 0; j--) //从后往前是为了滚动数组做准备
        {
            if(t[i] > j)
                dp[i][j] = dp[i-1][j];
            else
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
        }
    }
 
    cout << dp[M][T];
    return 0;
 
}
 
 
 
 
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/

方法四:状态转移+滚动数组

        减少了备忘录所需的空间,思考方式与之前做的数字三角形一样。

#include <iostream>
#include <algorithm>
#include <cstdio>
 
using namespace std;
 
int t[110]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
 
int dp[1010] = {0};//代表取前i种药品,在时间为j时的最大价值***
 
int T; //可以使用的时间
int M; //药材个数
 
 
int KnapSack(int i, int j);
 
int main()
{
    //输入总时间T和物品总数M
    cin >> T >> M;
    for(int i = 1; i <= M; i++)
    {
        //输入每个药材的时间和价值
        cin >> t[i] >> v[i];
    }
 
 
    for(int i = 1; i <= M; i++)
    {
        for(int j = T; j >= 0; j--)
        {
            if(t[i] > j)
                dp[j] = dp[j];
            else
                dp[j] = max(dp[j],dp[j-t[i]]+v[i]);
        }
    }
 
    cout << dp[T];
    return 0;
 
}
 
 
 
 
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/

如果对您有帮助麻烦点个赞哦~。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百年bd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值