01背包与完全背包

01背包与完全背包

01背包

题目:

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

i i i 种物品的体积是 v i v_i vi,价值是 w i w_i wi

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

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品种数和背包容积。

接下来有 N N N 行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。

输出格式

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

数据范围

0 < N , V ≤ 10000 < N , V ≤ 1000 0<N,V≤10000<N,V≤1000 0<N,V10000<N,V1000
0 < v i , w i ≤ 10000 < v i , w i ≤ 1000 0<vi,wi≤10000<vi,wi≤1000 0<vi,wi10000<vi,wi1000

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

递归 + 记忆化思路:

递归枚举每一种可能

对于每一个物品 当我们背包剩余容量大于物品体积时 就有两种可能 拿或不拿


d p ( n + 1 , s u m − v [ n ] ) + v [ n ] dp(n+1,sum-v[n])+v[n] dp(n+1,sumv[n])+v[n]
不拿
d p ( n + 1 , s u m ) dp(n+1,sum) dp(n+1,sum)
两种可能取最大值即可

当背包容量小于物品体积时 只有不拿一种可能 直接找下一个即可

核心代码

	//背包剩余容量小于当前物品体积 直接找下一个
    if (sum < v[n]) return m[n][sum][0] = dp(n + 1, sum);

	//背包剩余容量大于等于当前物品体积 返回拿与不拿两种可能的最大值
    m[n][sum][0] = max(dp(n + 1, sum), dp(n + 1, sum - v[n]) + w[n]);
    return m[n][sum][0];

递归 + 记忆化:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
const ll MAX = 1e5 + 5;

int m[1001][4999][2] = {0};//记忆化
int v[1010] = {0};//体积
int w[1010] = {0};//价值
int len;//物品数量
int S;//背包最大容量


//  枚举到第几个物品 背包剩余容量
int dp(int n, int sum) {
    if (n > len) return 0;//递归出口
    if (m[n][sum][1] == 1) return m[n][sum][0];
    m[n][sum][1] = 1;
    
    //背包剩余容量小于当前物品体积 直接找下一个
    if (sum < v[n]) return m[n][sum][0] = dp(n + 1, sum);

    m[n][sum][0] = max(dp(n + 1, sum), dp(n + 1, sum - v[n]) + w[n]);
    return m[n][sum][0];//背包剩余容量大于等于当前物品体积 返回拿与不拿两种可能的最大值
}

int main() {
    cin>>len>>S;
    for (int i = 1; i <= len; i++) cin >> v[i] >> w[i];
    cout<<dp(1,S)<<endl;
    return 0;
}

动态规划:

定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为 只看前 i i i个物品,在背包容量为 j j j的情况下的最大可能

同样的

如果 j < v [ i ] j<v[i] j<v[i]
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]
j > = v [ i ] j>=v[i] j>=v[i]时 两种可能取最大值
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i-1][j], dp[i - 1][j - v[i]] + w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])
最后 d p [ N ] [ V ] dp[N][V] dp[N][V]就是所有物品体积最大为 V V V时的最大值

二维:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
const ll MAX = 1e5 + 5;

int main() {

    int dp[1010][1010] = {0};
    int N, V;//物品数量  最大体积

    int v[1010] = {0};//体积
    int w[1010] = {0};//价值

    cin >> N >> V;
    for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];


    //循环枚举 前 i 个物品 最大体积为 j 的情况下下最大值
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= V; 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]);
            }
        }
    }

    cout << dp[N][V] << endl;

    return 0;
}

优化:

上面的二维版本不难发现我们只使用了 d p [ i ] dp[i] dp[i] d p [ i − 1 ] dp[i-1] dp[i1]的状态
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) ; dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]); dp[i][j]=max(dp[i][j],dp[i1][jv[i]]+w[i]);
所有优化一维成一维数组可以直接把 [ i ] [i] [i]去掉

定义 d p [ j ] dp[j] dp[j]为所有物品重量为 j j j时的最优解

并且使用 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]时我们要维护 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]为上一轮也就是 d p [ i − 1 ] [ j − v [ i ] ] dp[i - 1][j - v[i]] dp[i1][jv[i]]的值
d p [ j − v [ i ] ] = d p [ i − 1 ] [ j − v [ i ] ] dp[j-v[i]]=dp[i - 1][j - v[i]] dp[jv[i]]=dp[i1][jv[i]]
所以我们 j j j得从大到小枚举

这样 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]一定是当前还没有枚举过的,也就是上一轮的值

这样的话 d p [ j − v [ i ] ] dp[j-v[i]] dp[jv[i]]一定等于之前二维的 d p [ i − 1 ] [ j − v [ i ] ] dp[i - 1][j - v[i]] dp[i1][jv[i]]

优化后的一维:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
const ll MAX = 1e5 + 5;

int main() {

    int dp[1010] = {0};
    int N, V;//物品数量  最大体积

    int v[1010] = {0};//体积
    int w[1010] = {0};//价值

    cin >> N >> V;
    for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];


    //循环枚举 前 i 个物品 最大体积为 j 的情况下下最大值
    for (int i = 1; i <= N; i++) {
        //体积从大到小枚举
        for (int j = V; j >= v[i]; j--) {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[V] << endl;

    return 0;
}

完全背包

题目:

N N N 种物品和一个容量是 V V V 的背包,每种物品都有无限件可用。

i i i 种物品的体积是 v i v_i vi,价值是 w i w_i wi

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

输入格式

第一行两个整数, N , V N,V NV,用空格隔开,分别表示物品种数和背包容积。

接下来有 N N N 行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。

输出格式

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

数据范围

0 < N , V ≤ 10000 < N , V ≤ 1000 0<N,V≤10000<N,V≤1000 0<N,V10000<N,V1000
0 < v i , w i ≤ 10000 < v i , w i ≤ 1000 0<vi,wi≤10000<vi,wi≤1000 0<vi,wi10000<vi,wi1000

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

思路:

这个题目与01背包非常相似,只是物品可以无限次使用

定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为前 i i i件物品无限使用,体积最大为 j j j的情况的最大值

j < v [ i ] j<v[i] j<v[i]时 也就是不使用当前物品 转换与01背包一样
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]

j > = v [ i ] j>=v[i] j>=v[i]

前面01背包使用转移为
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])
分拿与不拿两种情况 拿了就不能再拿了 ,所以就是 d p [ i − 1 ] [ j − v [ i ] ] dp[i-1][j-v[i]] dp[i1][jv[i]]

但是这个题是可以再拿 所以 i i i不用减一

所以完全背包 j > = v [ i ] j>=v[i] j>=v[i]的转移为
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i ] [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]) dp[i][j]=max(dp[i][j],dp[i][jv[i]]+w[i])

代码:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
const ll MAX = 1e5 + 5;


int main() {

    int dp[1010][1001] = {0};
    int N, V;//物品数量  最大体积

    int v[1010] = {0};//体积
    int w[1010] = {0};//价值

    cin >> N >> V;
    for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];


    //循环枚举 前 i 个物品 最大体积为 j 的情况下下最大值
    for (int i = 1; i <= N; i++) {
        for (int j = 0; j <= V; j++) {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i]) {
                dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
            }
        }
    }

    cout << dp[N][V] << endl;

    return 0;
}

优化:

前面01背包优化成一维需要维护 d p [ i − 1 ] [ j − v [ i ] ] dp[i-1][j-v[i]] dp[i1][jv[i]]的值,所以 j j j需要从大到小枚举

但是完全背包不需要维护 d p [ i − 1 ] [ j − v [ i ] ] dp[i-1][j-v[i]] dp[i1][jv[i]]的值

所以只需要把 j j j的枚举改成从小到大即可

01背包:
f o r ( i n t j = V ; j > = v [ i ] ; j − − ) for\quad(int\quad j\quad =\quad V; j\quad >= \quad v[i];\quad j--) for(intj=V;j>=v[i];j)
完全背包:
f o r ( i n t j = v [ i ] ; j < = V [ i ] ; j + + ) for\quad(int\quad j\quad =\quad v[i]; j\quad <= \quad V[i];\quad j++) for(intj=v[i];j<=V[i];j++)

代码:

#include<bits/stdc++.h>

using namespace std;
#define ll long long
const ll MAX = 1e5 + 5;

int main() {

    int dp[1010] = {0};
    int N, V;//物品数量  最大体积

    int v[1010] = {0};//体积
    int w[1010] = {0};//价值

    cin >> N >> V;
    for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];


    //循环枚举 前 i 个物品 最大体积为 j 的情况下下最大值
    for (int i = 1; i <= N; i++) {
        //体积从小到大枚举
        for (int j = v[i]; j <= V; j++) {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[V] << endl;

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值