❤️详解经典动态规划算法「背包问题」

目录

1-01背包

2.完全背包

3.多重背包问题

4.分组背包问题


1-01背包

问题描述

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

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

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

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

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

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

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8

01背包问题是背包问题的核心思想,理解01背包,以外的背包问题自然迎刃而解

解题思路

1.定义 dp[N][N] 二维数组。 dp[i][j] 表示:只考虑前 i 个物品,背包容量在j的情况下的最大总价值

2.那么题目所求的答案就是  dp[n][v] :考虑n个物品,背包容量为v的情况下的最大总价值

3.由动态规划的思想,将大问题转化为许多子问题,由子问题层层递推到最终问题的答案。递推的过程关键是得出状态转移方程。 那么 分析得到 dp[i][j]可以由 dp[i-1][j-vi] 得到。

状态转移方程为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-vi]+w[i])

含义:dp[i-1][j] 则是 不选第i个物品的情况下 只考虑前i-1个物品的价值 。

          dp[i-1][j-vi]+w[i] 则是 选第i个物品,那么总容量就要减去第i个物品的体积v[i] ,再去考虑前i-1个物品时的最大价值。 选第i个物品,总价值则要加上第i个物品的价值w[i]

4.由前面的定义知 dp[0][0]=0。表示考虑0个物品 容量为0 那么总价值一定为0, 从dp[0][0]开始递推 在容量为 1~ j 的情况下 考虑是否选 第 1个物品 ,依次类推 到 在1~j的容量下 考虑是否选第 n个物品 ,取 选 或者不选 情况下的价值最大情况。

代码如下:

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

const int N=1010;

int dp[N][N];
int v[N],w[N],n,m;

int main()
{
    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]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
         else dp[i][j]=dp[i-1][j];
     }
     
     cout<<dp[n][m]<<endl;
    return 0;
}

5.对空间进行优化 由上面的递推关系可知:每一层的答案都是由上一层推来的,和上上一层没有关系,那么就可以去掉一维,用一维度的滚动数组来实现以上功能

状态转移方程简化为:dp[j]=max(dp[j],dp[[j-vi]+w[i])

代码如下:

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

const int N=1010;
int n,m,dp[N];
int v[N],w[N];

int main()
{
    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=m;j>=v[i];j--)
          dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
          
    cout<<dp[m]<<endl;
    return 0;
}

2.完全背包

题目描述

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

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

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

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

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

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

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

1.完全背包问题思路 和 01背包大同小异,差别在于每一个物品可以取无数次 直到取到 容量不够的情况下为止。

状态转移方程:dp[i][j] = max(dp[i][j],dp[i][j-k*v[i]]+k*w[i])  (k从0开始)

代码如下:

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

const int N=1010;
int n,m,v[N],w[N];
int dp[N][N];

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

2.跟01背包一样 进行 空间优化 dp[j] = max(dp[j],dp[j-k*v[i]]+k*w[i])

代码如下:

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

const int N=1010;
int n,m,v[N],w[N];
int dp[N];

int main()
{
    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=m;j>=0;j--)
          for(int k=0;k*v[i]<=j;k++)
              dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
           
    cout<<dp[m]<<endl;
    return 0;
}

3. 再次分析可知 考虑第i个物品时,当第二次拿这个物品,相当于 在第一次拿这个物品的情况又拿了一次,第三次拿则相当于在第二次拿的情况下再拿一次。

则状态转移方程为: dp[j] = max(dp[j],dp[j-v[i]]+w[i])

代码如下:

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

const int N=1010;
int n,m,v[N],w[N];
int dp[N];

int main()
{
    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=v[i];j<=m;j++)
              dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
           
    cout<<dp[m]<<endl;
    return 0;
}

3.多重背包问题

题目描述

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

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

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

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

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

1.多重背包可以按完全背包的情况进行考虑 ,每一个物品都考虑 拿 1~k 次,从中取最大值保存

2.因为物品数量有限, 因此遍历1~j的容量大小时,物品数并不一定是递增的(因为物品数有限,可能已经没了)

朴素代码如下:

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

const int N=110;
int n,m,dp[N];
int v[N],w[N],s[N];

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

3.但这种代码有三层循环,时间复杂度较高,可以使用是二进制优化法进行优化

思路:将每一个物品 的数量按 2^1 2^2 2^3 ...[log k], k-前项和。 这些分组正好可以组合成 第 i个物品 分别取1 ~ k 次的方案。那么就可以将此类背包问题转化为 0 1 背包问题了。

代码如下:

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

typedef pair<int,int> PI;
const int N=10010;
int n,m,dp[N];
vector<PI> goods;

int main()
{
    cin>>n>>m;
    
    while(n--)
    {
       int v,w,s;
       cin>>v>>w>>s;
       
       for(int i=1;s>=i;i<<=1)
       {
           goods.push_back({v*i,w*i});
           s-=i;
       }
       
       if(s>0) goods.push_back({s*v,s*w});
    }
    
    for(auto i: goods)
       for(int j=m;j>=i.first;j--)
       {
           dp[j]=max(dp[j],dp[j-i.first]+i.second);
       }
       
    cout<<dp[m]<<endl;
    return 0;
}

4.分组背包问题

问题描述

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

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

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8

 1.解题思路,分组背包问题比较简单,解题思路就是 从每一个组中选物品 ,取选法的最大值,依次递推下去,如果把每一个组想象成一个物品,那么就是01背包问题了。

代码如下:

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

const int  N=110;
int n,m,dp[N];
int v[N][N],w[N][N],s[N];

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

猜你喜欢

转载自blog.csdn.net/weixin_40582034/article/details/120397482