leetcode(17):背包9讲||总结

该篇文章从网络搜刮的+自己的理解;
自己在学。
本文主要参考资料——传送门


练习题:
零钱兑换322

01背包


第一讲 01背包问题

这是最基本的背包问题,每个物品最多只能放一次。

第二讲 完全背包问题

第二个基本的背包问题模型,每种物品可以放无限多次。
第三讲 多重背包问题

每种物品有一个固定的次数上限。
第四讲 混合三种背包问题

将前面三种简单的问题叠加成较复杂的问题。
第五讲 二维费用的背包问题

一个简单的常见扩展。
第六讲 分组的背包问题

一种题目类型,也是一个有用的模型。后两节的基础。
第七讲 有依赖的背包问题

另一种给物品的选取加上限制的方法。
第八讲 泛化物品 第九讲 背包问题问法的变化

试图触类旁通、举一反三。
附录一:USACO中的背包问题

给出 USACO Training 上可供练习的背包问题列表,及简单的解答。 附录二:背包问题的搜索解法

除动态规划外另一种背包问题的解法。

1. 01背包问题

1.1 问题描述

题目
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

1.2 基本思路

  • 基本思路
    这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

  • 动规
    用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

这个地方可以参考 背包九讲——全篇详细理解与代码实现

该方法的时间复杂度为O(N*V)

具体的原理就不在这里介绍,这里主要用来整理归纳实现方法;

  • 未优化代码
//假定 物品数量为N(Num简写 方便记忆),背包容量为V(Volumn简写)
//假定 第i个物品重量和价值分别为 W[i](Weight简写)、C[i](Cost简写)

//初始化
vector< vector<int> > dp(n,vector<int>(V,0)); //所有值初始化为0
//int dp[1000][1000] = 0;

for(int i=1;i<=N;i++)
{
	for(int j=1;j<=V;j++)
	{
		//如果第i个物品 不选的话,前i个商品放入容积为j最大价值 与 前i-1个商品放入背包中的价值一样;
		//此时背包应该没有约束 容量为j
		f[i][j] = f[i-1][j]; 
		if(j>=W[i])//如果当前容量 还够用 则可以选择第i个物品
		{
			//选的话,在前i-1个商品时,背包容量需要为[j-W[i]];
			//此时的f[i][j]在上面已经赋值了f[i-1][j]
			f[i][j] = max(f[i][j],f[i-1][j-W[i]]+C[i]);
		}
	
	}
}

更新顺序图例:
在这里插入图片描述


刷表的时候顺序:
前1个物品放入0的背包时最大价值
前1个物品放入1的背包时最大价值
前1个物品放入2的背包时最大价值
… … … . …
前1个物品放入V的背包时最大价值
|
前2个物品放入1的背包时最大价值
前2个物品放入2的背包时最大价值
前2个物品放入V的背包时最大价值
|
前3个物品放入1的背包时最大价值
前3个物品放入2的背包时最大价值
前3个物品放入V的背包时最大价值
|
前N个物品放入1的背包时最大价值
前N个物品放入2的背包时最大价值
前N个物品放入V的背包时最大价值


  • 优化后的代码
//初始化
//全局变量定义在堆里面,堆里面的值会被初始化为0
int dp[1000][1000]={0};
for(int i=1;i<=N;i++)
{
	for(int j=V;j>=0;j--)
	{
		f[j] = max(f[j],f[j-W[i]]+C[i]);
	}
}

1.3 初始化问题

最优解背包问题中,存在两种问法

  • 有的题目要求"恰好装满背包"时的最优解
  • 有的题目则并没有要求必须把背包装满

这两种问法的区别是在初始化的时候有所不同。

  • 如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1…V]均设为−∞,这样就可以保证最终得到的f[N]f[N]是一种恰好装满背包的最优解。
  • 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0…V]全部设为0。

可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是−∞了。
如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。

1.4 模板代码

#include<iostream>
using namespace std;

const int N = 1010;

int v[N], w[N], dp[N];//dp[N][N]

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

2 完全背包问题

2.1 题目描述

题目
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。每件物品数量不限,求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

2.2 基本思路

该方法就是将01背包的j、循环正序写就行;

#include<iostream>
using namespace std;

const int N = 1010;

int v[N], w[N], dp[N];//dp[N][N]

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

类比01背包问题;状态转移方程:
在这里插入图片描述


  • 简单小优化
    在这里插入图片描述

但是上面需要循环三类;

for(int i=1;i<=N;i++)
	for(int j=1;j<=M;j++)
		for(int ii = 1;ii<=k;ii++)
			//write code here
			

复杂度就非常高了;


将完全背包问题转换为01背包问题求解
在这里插入图片描述如上图所示,但是并没有改变时间复杂度


二进制转化方法:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

正序倒序举例参考文章

3 多重背包问题

3.1 问题描述

题目
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。第i件物品共有n[i]件可用,求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

3.2 基本思路

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

3.3 拆分代码

在这里插入图片描述

3.4 模板代码

普通代码:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int n,m;
    cin>>n>>m;
    vector<int> f(m+1,0);
    int v,w,s;
    for(int i=1;i<=n;i++)
    {
        cin>>v>>w>>s;
        for(int j=m;j>=0;j--)
        {
            
            for(int ii = 0;ii<=s;ii++)
            {
                if(j>=ii*v)
                f[j] = max(f[j],f[j-ii*v]+ii*w);
            }
            
                }
    }
    cout<<f[m]<<endl;
        return 0;
}

二进制优化代码:

#include<iostream>
#include <vector>
using namespace std;

int main()
{
    int n,m;
    cin>>n>>m;
    vector<int> datav,dataw;
    int vv,ww,ss;
    for(int i=0;i<n;i++)
    {
        cin>>vv>>ww>>ss;
        for(int ii = 1;ii<ss;ii*=2)
        {
            datav.push_back(ii*vv);
            dataw.push_back(ii*ww);
            ss-=ii;
        }
        if(ss>0)
            {
                datav.push_back(ss*vv);
                dataw.push_back(ss*ww);
            }
        
        
    }
    vector<int> f(m+1,0);
    for(int i=0;i<datav.size();i++) // the num of goods should be updated
    {
        for(int j=m;j>=datav[i];j--)
        {
            f[j] = max(f[j],f[j-datav[i]]+dataw[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

4. 混合背包问题

分组就行;
在这里插入图片描述

4.1 模板代码

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    int n,m;
    cin>>n>>m;
    vector<int> f(m+1,0);
    
    vector<int> v,w,s;
    int tempv,tempw,temps;
     for(int i=0;i<n;i++)
     {
         cin>>tempv>>tempw>>temps;
         
         if(temps == -1)
         {
            s.push_back(1);
            v.push_back(tempv);
         w.push_back(tempw);
         }
         else if(temps>0)
         {
             for(int ii = 1;ii<=temps;ii*=2)
             {
                 s.push_back(ii);
                 v.push_back(tempv*ii);
                 w.push_back(tempw*ii);
                 
                 temps-=ii;
             }
             if(temps>0)
             {
                 s.push_back(temps);
                 v.push_back(tempv*temps);
                 w.push_back(tempw*temps);
             }
         }else
            s.push_back(temps),v.push_back(tempv),
         w.push_back(tempw);
     }
     
     for(int i=1;i<=s.size();i++)
     {
         if(s[i-1] ==0) //完全背包
         {
             for(int j=1;j<=m;j++)
             {
                 if(j>=v[i-1])
                    f[j]=max(f[j],f[j-v[i-1]]+w[i-1]);
             }
         }else //01
         {
             for(int j=m;j>=0;j--)
             {
                 if(j>=v[i-1])
                     f[j] = max(f[j],f[j-v[i-1]]+w[i-1]);
             }
         }
     }
     
     cout<<f[m]<<endl;
     return 0;
}

5. 二维背包问题

多一层循环就行

在这里插入图片描述

5.1 模板代码

#include <iostream>
#include <vector>

using namespace std;

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

10. 求方案总数的问题

dd大佬的讲解中最后也有介绍;下面是个截图

在这里插入图片描述

for i=1..N
   for v=0..V
       f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
       g[i][v]=0
       if(f[i][v]==f[i-1][v])
           inc(g[i][v],g[i-1][v]
       if(f[i][v]==f[i-1][v-c[i]]+w[i])
           inc(g[i][v],g[i-1][v-c[i]])

在这里插入图片描述在这里插入图片描述
伪代码:

  F[0][]0
 
  F[][0]0
 
  G[][ ]1
 
  for i ← 1 to N
 
      do for j ← 1 to V
 
          F[i][j] ← F[i-1][j]
 
          G[i][j] ← G[i-1][j]
 
          if (j >= C[i])
 
              if (F[i][j] < F[i][j-C[i]]+W[i])
 
                  then F[i][j] ← F[i][j-C[i]]+W[i]
 
                       G[i][j] ← G[i][j-C[i]]
 
              else if (F[i][j] = F[i][j-C[i]]+W[i])
 
                  then G[i][j] ← G[i-1][j]+G[i][j-C[i]]
 
  return F[N][V] and G[N][V]
 

    F[0][0]1  
      
        for i ← 1 to N  
      
                do for j ← 0 to V  
      
                if (j < C[i])  
      
                            then F[i][j] ← F[i-1][j]  
      
                else  
      
                          F[i][j] ← F[i-1][j]+F[i][j-C[i]]  
      
        return F[N][V]  

模板代码

        dp[0]=1;
        for(int i=1;i<=N;i++)
        {
            for(int j=v[i];j<=V;j++)
            dp[j]=dp[j]+dp[j-v[i]];
        }

1000. 另外穿插一个找零钱问题

1000.1 题目简介

一般找零钱和背包问题很像;
输入:总钱数,和拥有的金币种类(1元,2元,5元,10元,20元等);
输出:需要的最少硬币数

扩展:
每种硬币只能取一类;
每种硬币可以无限取;
每种硬币有上限(s[i])
有的硬币可以无限取,有个硬币只有一个和多个;

1000.2 基本思路

乍一看和背包问题是一模一样。零钱的背包容量V 就是 金币总数
每种物品的体积v 就是他的面额;需要注意的就是:只能是恰好装满

所需要统计的就是硬币数量因此 f[i] = max(f[i],f[i-v]+1);
具体分析:
定义f[ i ] [ j ] 为第i个硬币恰好装满amount数量的总额

则分为 选 和 不选
选 : f[i][j] = f[i-1][j-v[i]]+1;
不选: f[i][j] = f[i-1][j];

所以讲道理来说;代码应该和背包问题差不多;

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        if(coins.size()==0||amount ==0)
            return 0;
        vector<int> f(amount+1,99999);
        f[0] = 0;
        for(int i=0;i<coins.size();i++)
        {
            for(int j=1;j<=amount;j++)
            {
                if(j>=coins[i])
                    f[j] = min(f[j],f[j-coins[i]]+1);
            }
        }
        if(f[amount]==99999)
            return -1;
        return f[amount];

    }
};

1000.3 华为多维找零钱问题0715

在这里插入图片描述

//华为笔试题0715

int main()
{
    vector<int> coins = {1, 3, 7, 11, 13};
    vector<int> nums = {1,2,3,4,5};
    int m = 30;

    //多维背包问题 --- 二进制优化
    vector<int> v, w;
    for (int i = 0; i <= coins.size(); i++)
    {
        for (int k = 1; k <= nums[i];k*=2)
        {
            v.push_back(k * coins[i]);
            w.push_back(k); //个数也要统计

            nums[i] -= k;
        }
        if(nums[i]>0)
        {
            v.push_back(nums[i] * coins[i]);
            w.push_back(nums[i]);
        }
    }

    vector<int> f(m + 1, 9999);
    f[0] = 0;
    for (int i = 0; i < v.size();i++)
    {
        for (int j = m; j >= v[i];j--)
        {
            f[j] = min(f[j], f[j - v[i]] + w[i]);
        }
    }

    if(f[m] == 9999)
        cout << -1 << endl;
    cout << f[m] << endl;

    return 0;
}

本文参考资料:
1.单调队列优化多重背包问题
2.各种背包问题
3.背包九江
4.背包玖讲
5.背包九江
6.单队列优化多重背包
7. 背包9讲
8. 9讲
9. dd大佬九江
10.哔哩哔哩教学视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CoomCon

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

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

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

打赏作者

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

抵扣说明:

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

余额充值