面试常见的01背包、多重背包、完全背包

一个不错的《背包九讲》的赏析博客。https://blog.csdn.net/yandaoqiusheng/article/details/84782655#commentBox

一、01背包

 HihoCoder - 1038 

https://vjudge.net/problem/HihoCoder-1038

题目

且说上一周的故事里,小Hi和小Ho费劲心思终于拿到了茫茫多的奖券!而现在,终于到了小Ho领取奖励的时刻了!

小Ho现在手上有M张奖券,而奖品区有N件奖品,分别标号为1到N,其中第i件奖品需要need(i)张奖券进行兑换,同时也只能兑换一次,为了使得辛苦得到的奖券不白白浪费,小Ho给每件奖品都评了分,其中第i件奖品的评分值为value(i),表示他对这件奖品的喜好值。现在他想知道,凭借他手上的这些奖券,可以换到哪些奖品,使得这些奖品的喜好值之和能够最大。

分析

抽象:

 背包容量 m     1e5

 石头个数 n     500

 石头的重量为 need(i) 2e5

 价值为    value(i) 1e3

设dp[i][j]表示第i个石头(前i个石头)、背包空间剩余j时可达到的最大收益。

有方程:

dp[i,j]=     max(   放:dp[i-1,j-w[i]] + v[i]    (条件为wi<=j),

                          不放:dp[i-1,j]

                        );

1<=i<=n

1<=j<=m

观察方程,与i无关,且每个方程需要得知对应j较小的答案。故空间优化:

dp[j]=     max(   放:dp[j-w[i]] + v[i]    (条件为wi<=j),

                          不放:dp[j]

                        );

1<=i<=n

m>=j>=1 (倒序)

ac代码

#include <iostream>
#include <stdlib.h>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int d[maxn];
/*
 背包容量 m     1e5
 石头个数 n     500
 石头的重量为 need(i) 2e5
 价值为    value(i) 1e3
 
 */
int main(int argc, const char * argv[]) {
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        scanf("%d%d",w+i,v+i);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1 && w[i]<=j;j--){
            d[j]=max(d[j-w[i]]+v[i],d[j]);
          
        }
    }
    cout<<d[m]<<endl;
    return 0;
}

二、多重背包

题目

A - 悼念512汶川大地震遇难同胞――珍惜现在,感恩生活

 HDU - 2191 

急!灾区的食物依然短缺! 
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。 
请问:你用有限的资金最多能采购多少公斤粮食呢?

分析

混合背包(多重背包问题),可直接转化为01背包问题,(也可用二进制优化)。

当然,还有O(vn)的方法,需要用到单调队列。具体见背包九讲。此处不列举了。

#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int dp[200];
int c[maxn];
/*
 背包容量 n     100
 种类个数 m     100
 石头个数cnt    2000
重量w 20 价值v 200 个数c 20
 */
int cnt;
inline void func(int x,int y,int z){
    int wei=1;
    while(z){
        if(wei>z){
            wei=z;
        }
        w[++cnt]=wei*x;
        v[cnt]=wei*y;
        z-=wei;
        wei=wei<<1;
    }
}
int main(int argc, const char * argv[]) {
    int n,m;
    int T;
    cin>>T;
    int x,y,z;
    while(T--){
        cnt=0;
        cin>>n>>m;
        memset(dp, 0,sizeof(dp));
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            func(x,y,z);
        }
        for(int i=1;i<=cnt;i++){
            for(int j=n;j>=1 && j>=w[i];j--){
                dp[j]=max(dp[j-w[i]]+v[i],dp[j]);
            }
        }
        cout<<dp[n]<<endl;
        
    }
   
    return 0;
}

三、完全背包

问题:

完全背包

 HihoCoder - 1043 

有n种不同种类的硬币,每种都有无限个。每种硬币的价值不同,重量不同。现在你有一个袋子最多可装m的重量,求在满足重量要求的情况下得到的最大价值是多少。

解析

完全背包问题。相比于01背包比较“取”和“不取”,完全背包需要比较"取多少",所以需要多一层循环。

设重量为wi ,价值为vi

基本方程为:

定义:dp[i,j]表示前i种硬币、袋子剩余容量为j时,可以得到的最大价值。

方程:

dp[i,j]=max(     dp[i-1,j-k*w[i]]+v[i]*k    ) ,  0<=k<inf  &&  w[i]*k<=j

1<=i<=n

1<=j<=m 

空间压缩后:

dp[j]=max(     dp[j-k*w[i]]+v[i]*k    ) ,  0<=k<inf  &&  w[i]*k<=j

1<=i<=n

m>=j>=w[i]

AC代码:

//
//  main.cpp
//  test
//
//  Created by dawn on 2019/8/26.
//  Copyright © 2019 chuyi. All rights reserved.
//

#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int dp[maxn];
/*
 总容量 m 石头种类数n
 价值v
 重量w
 */

int main(int argc, const char * argv[]) {
    int n,m;
    cin>>n>>m;
    memset(dp, 0,sizeof(dp));
    for(int i=1;i<=n;i++){
        scanf("%d%d",w+i,v+i);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=w[i];j--){
            int max_k = j/w[i];
            for(int k=0;k<=max_k;k++){
                dp[j]=max(
                          dp[j],
                          dp[j-k*w[i]]+v[i]*k
                          );
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

优化?

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值