dp——背包模板乱锅炖

 

目录

1.经典背包模板

 01背包

完全背包

 

多重背包

混合背包

分组背包

二维费用背包

有依赖的背包

2.背包问题分类

求最大/小费用

求最大/小方案数

3.巧妙转化使用背包模板

279. 完全平方数

322. 零钱兑换

474. 一和零

494. 目标和

416. 分割等和子集

美团考试真题



学会从算法中总结套路可以少走很多弯路,避免盲目刷题,很多算法题或许也是通过最初的那几个题目改来改去套路都差不多的。在leetcode刷题的时候发现很多题目都可以转化为背包问题来解决所以写了这篇博客,如错误之处请指教。


1.经典背包模板

 01背包

方便理解版

最大费用,f[i][j]表示把前i件物品放入限制(可以是体积重量或者其他任何限制)为j的背包所获得的的最大收益,
j<w[i]  不放入第i件物品    f[i][j] = f[i-1][j] 
j>=w[i] 可以放入第i件物品  f[i][j] = max( f[i-1][j] , f[i-1][j-w[i]] + v[i] ) 

for(int i=1;i<=n;i++){
    for(int j=1;j<=V;j++){
        if(j>=w[i]){
            f[i][j] = max( f[i-1][j] , f[i-1][j-w[i]] + v[i] );
        }
        else{
            f[i][j] = f[i-1][j] ;
        }

滚动数组优化,这种其实不好理解

最大费用,f[j]表示把前i件物品放入限制(可以是体积重量或者其他任何限制)为j的背包所获得的的最大收益,
f[j] = max( f[j] , f[j-w[i]] + v[i] ) 

for(int i=1;i<=n;i++){
    for(int j=V;>=w[i];j--){
        f[j] = max( f[j] , f[j-w[i]] + v[i] ) ;
        

完全背包


int w[MAXN];    // 重量 
int v[MAXN];    // 价值 
int dp[MAXN][MAXN]={0};  // f[i][j], j重量下前i个物品的最大价值 

//dp[i,j] = max(dp[i-1,j-k*w[i]] + k*v[i])  0<=k*w[i]<=j
int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> w[i] >> v[i];
    
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++)
        { 
            for(int k=0;k*w[i]<=j;k++){
                dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
            }
            
        }
           
    }   
    cout<<dp[n][m]<<endl;
    return 0;
}

 

int w[MAXN];    // 重量 
int v[MAXN];    // 价值 
int dp[MAXN][MAXN]={0};  // f[i][j], j重量下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> w[i] >> v[i];
    
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++)
        { 
            
            dp[i][j] = dp[i-1][j];
            //不
            if(j>=w[i]){
                dp[i][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]);
            }
                
            //dp[i-1][j]表示不放第i件物品,第i-1行
            //dp[i][j-w[i]]+v[i] 放第i件物品第i行
            
        }
           
    }   
    cout<<dp[n][m]<<endl;
    return 0;
}

多重背包

二进制优化为01背包,理论依据任何一个数都可以由 2^0,2^1,2^2,...,的多项式组成,任何一个10进制数对应且唯一对应一个二进制数,2^0+2^1+2^2+...+2^n<=m,将原来的m个物品分解为log2(m)个物品,

S[i]*w[i] = (w[i] + 2^2*w[i] + 2^2*w[i] + ... ) 共有log2(m)个物品,也就是说着S[i]个物品的任意组合价值可以通过这log2(m)个物品的特定组合价值得到。

混合背包

分别计算01背包和完全背包多重背包

分组背包

二维费用背包

跟01背包一样的只不过是多疑了一个限制,一次类推还可以是三维费用背包

有依赖的背包

2.背包问题分类

背包问题有两种不同的变形,一种是背包全部装满,另一种就不必装满,这两种情况实现的区别主要是初始化的不同,分为以下几种:

(1) 不要求背包全部装满,初始化f[0:V]=0,

因为所有的背包都有一个情况什么也不装就是f[0:V]=0

(2) 要求背包全部装满,初始化f[0]=0,f[1:V]=-MIN/MAX,

这里的MIN和MAX要根据情况而定,背包全部装满的情况只有V=0的时候可以什么也不装,其他情况都是待求的状态,如果要求最大值可以用一个很小的数初始化,如果要求最小值可以用一个较大的数初始化。

求最大/小费用

求最大/小方案数

3.巧妙转化使用背包模板

279. 完全平方数

https://leetcode-cn.com/problems/perfect-squares/

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/perfect-squares
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个题初看好像和背包问题没有什么关系,那么我们来看下这道题的另一种文字表述,沃日算法成了文字游戏题《^^》

现在有若干r个物品,重量为(1^2,2^2,...,r^2),每个物品的价值均为1,背包的总重量为n,
r = int(sqrt(n)).每个物品的数量不限,求在背包恰好装满的条件下的最小总价值。

是不是很熟悉,对的这就是一个经典的完全背包问题,只不过求的是最小价值。因为每个物品的价值是1,所以价值最小就等价于完全平方数的个数最小了。这里跟完全背包差不多的就是由于背包恰好装满,所以初始化f[0:V]=n+1;总价值肯定不会超过n+1的。而且f[0]=0;

322. 零钱兑换

https://leetcode-cn.com/problems/coin-change/

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

 

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

背包表述

给定不同重量(coins)的物品,背包总重量为amount,每个物品的价值均为1,
每个物品的数量不限,求这些物品恰好装满背包的最小价值,如果不能恰好装满,返回-1.

这个题跟上面的其实是一样的思路,只是这里存在不能恰好装满的情况时需要返回-1.

474. 一和零

https://leetcode-cn.com/problems/ones-and-zeroes/

在计算机界中,我们总是追求用有限的资源获取最大的收益。

现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。

注意:

给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
示例 1:

输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4

解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ones-and-zeroes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个题其实可以看做一个二维费用背包问题,所谓的二维就是除了费用外的其他限制有2个,比如重量和体积,还可以是3维以上,

给定若干个物品(对应题目中的字符串),每个物品至多装一个,每个物品的价值均为1,
已知背包的重量m和体积n,求恰好装满的情况下最大价值。

494. 目标和

https://leetcode-cn.com/problems/target-sum/

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个题目相对技巧性有点强,需要转个弯,我们把前面加+的数集合记为M,加-的记为N,则有sum(M)-sum(N) = S

sum(M) = (sum(M,N) + S) / 2,这样就可以写出这题的背包表述记sum(M) = V,可以事先求出,

有一个nums数组记录每个物品的体积,背包的总体积是V,且每个物品最多只能用一次,求这些物品恰好装满背包的总方案数。

这里注意初始化边界dp[0] = 0

416. 分割等和子集

https://leetcode-cn.com/problems/partition-equal-subset-sum/

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

背包表述:

给定若干个物品的体积nums,背包的最大体积为v=sum(sums)/2,每个物品最多只能用一次,求是否存在恰好装满背包的方案?

很显然这是一个01背包,只是把目标变了一下

接下来我们看个美团真题也可以转化为01背包问题

美团考试真题

原题

小明同学在参加一场考试,考试时间2个小时。试卷上一共有n道题目,小明要在规定时间内,完成一定数量的题目。  考试中不限制试题作答顺序,对于 i 第道题目,小明有三种不同的策略可以选择:  (1)直接跳过这道题目,不花费时间,本题得0分。

(2)只做一部分题目,花费pi分钟的时间,本题可以得到ai分。  (3)做完整个题目,花费qi分钟的时间,本题可以得到bi分。 

小明想知道,他最多能得到多少分。

01背包描述

这个题的每个题目都可以看做一个广义的物品,用时可以看做广义的限制或者体积,重量的限制,
每道题的得分可以看做物品的重量,求把这些物品放到容量限制为120的背包中,最大总价值,很显然背包可以不装满,不过这道题每次选择有三种,(1) 一个都不装,(2) 装简单题,(3) 装复杂题。在dp里面加上三个条件就可以直接套模板
#include<bits/stdc++.h>

//01背包问题,t是可以看做W,
using namespace std;

int main(){
    
    int n;
    cin>>n;
    int dp[125]={0};
    for(int i=0;i<n;i++){
        int p,a,q,b;
        cin>>p>>a>>q>>b;
        for(int j=120;j>=0;j--){
            if(j>=p &&j>=q) //三种选择都可能
            dp[j] = max(max(dp[j],dp[j-p] + a),dp[j-q] + b);
            if(j>=p &&j<q) //不能做复杂题目
            dp[j] = max(dp[j],dp[j-p] + a);
            if(j<p &&j>=q) //不能做简单题目
            dp[j] = max(dp[j],dp[j-q] + b);
            
        }
    }
    cout<<dp[120];
    return 0;
}

猜你喜欢

转载自blog.csdn.net/firesolider/article/details/108117145