dp(3) - 背包问题(上)

目录

简论

关于dp问题 :

​编辑

0-1背包问题

定义 :

例题 :

题面 :

​编辑

思路 :

代码(二维) :

代码(一维优化版):

完全背包问题

题目链接 :

题面 :

​编辑

思路 :

代码(朴素) :

代码(优化) :

代码(一维优化) :

多重背包问题

题目链接 :

题面 :

​编辑

思考 :

代码 (朴素):

多重背包问题II

链接 :

思路 :

代码(二进制优化) :

分组背包问题

题目链接 :

思考 :

代码 (朴素):

代码(一维优化) :


简论

这篇将关于动态规划问题中的背包问题(上),包括,01背包,完全背包,多重背包,分组背包的朴素代码和其优化的详细解答,主要基于acwing算法基础课动态规划讲解,欢迎留言讨论!!!

知识图谱 :

1.代码随想录

dp(3) - 背包问题(上)_第1张图片

2.acwing

dp(3) - 背包问题(上)_第2张图片

  • 01背包问题 : N个物品V空间容量,每个物品仅能用一次,v[i],w[i],是总价值最大

  • 完全背包问题 : 每件物品有无数个

  • 多重背包问题 : 每个物品si个;

  • 分组背包 : M组

关于dp问题 :

  • 先考虑状态表示 : 如f(i,j),又可以分为集合和属性 :

    • 集合 : 如在01背包问题中,f(i,j)表示只从前i个物品中选且满足总体积<=j的所有选法;

    • 属性 : 本题求得是最大值,也就是max,其它得还有min,数量等;

  • 再考虑状态计算,这一部分也就是考虑状态转移方程式,即f(i,j)能由那个状态得来;

    • 对应集合的划分,表示能分成的子集的集合;

    • 在01背包问题中,f(i,j)可以划分为含i和不含i的两种情况;

dp(3) - 背包问题(上)_第3张图片

0-1背包问题

定义 :

给定一个容量为V的背包,现在有N个物品,每个物品的价值为vi,重量为wi,求选择一个或多个,再不超过容量的情况下,求每个物品最多选一次的前提下的最大价值;

例题 :

  • 2. 01背包问题 - AcWing题库

  • [NOIP2005 普及组] 采药 - 洛谷

题面 :

dp(3) - 背包问题(上)_第4张图片

思路 :

重要变量&公式解释 f(i,j):表示所有选法集合中,只从前i个物品中选,并且总体积 ≤ j的选法的集合;它的值是这个集合中每一个选法的最大值.

状态转移方程 f [i] [j] = max(f(i-1,j), f(i-1,j-v[i])+w[i])

f(i-1 , j) : 不选第i个物品的集合中的最大值 f(i-1,j-v[i]) + w[i] : 选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.

代码(二维) :

#include
using namespace std;
const int N = 1010;
int v[N],w[N];
int dp[N][N];
int main(){
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    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;
}

代码(一维优化版):

#include
using namespace std;
const int N = 1010;
int v[N],w[N];
int dp[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=m;j>=v[i];j--){
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout << dp[m] << endl;
    return 0;
}

完全背包问题

题目链接 :

3. 完全背包问题 - AcWing题库

题面 :

dp(3) - 背包问题(上)_第5张图片

思路 :

f(i,j)表示在前i个物品中选,且总体积不大于j的所有选法

dp(3) - 背包问题(上)_第6张图片

所以状态转移方程式 :

f(i , j) = f( i-1,j-v[i]*k) + w[i] * k

然后根据思路可以模拟出以下的朴素代码 (会超时):

代码(朴素) :

#include
#include
#include
using namespace std;
const int N = 1005;
int n,m;
int v[N],w[N];
int f[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++){
                f[i][j] = max(f[i][j] , f[i-1][j-v[i]*k]+w[i]*k);
            }
        }
    }
    
    cout << f[n][m] << endl;
    return 0;
}

代码(优化) :

上面代码有可能会超时( O(n^3) )

优化思路 :

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系: 
                        f[i][j]=max(f[i,j-v]+w , f[i-1][j]) 

然后根据 :

f[i][j] = max(f[i][j-v]+w,f[i-1][j]);

核心循环代码可以优化为 :

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

然后整个代码可以表示为:

#include
#include
#include
using namespace std;
const int N = 1005;
int n,m;
int v[N],w[N];
int f[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++){
            f[i][j] = f[i-1][j];
            if(j>=v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
        }
    }
    
    cout << f[n][m] << endl;
    return 0;
}

代码(一维优化) :

看前一步优化完的代码,先于01背包进行比较 :

f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包
​
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题

所以相同的进行一维优化 :

 for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }

优化完的整体代码 :

#include
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[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 = v[i] ; j<=m ;j++)
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }
    cout< 
  

多重背包问题

题目链接 :

4. 多重背包问题 I - AcWing题库

题面 :

dp(3) - 背包问题(上)_第7张图片

思考 :

dp :

状态表示 :

  • 集合 : 说有只从前i个物品中选,总体积不超过j的选法 , 表示成f(i,j)

  • 属性 : max

状态计算 :

  • f(i,j)的集合划分 : 根据第i个物品选多少个,最多选s[i]个,可划分为s[i]+1个集合;

  • 那么就可以确定状态转移方程式了:

    f[i][j] = max(f[i][j] , f[i-1][j-v[i]*k]+w[i]*k);
    k = 0,1,2...s[i];

代码 (朴素):

#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 105;
int n,m;
int v[N],w[N],s[N];
int f[N][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=0;j<=m;j++){
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
                f[i][j] = max(f[i][j] , f[i-1][j-v[i]*k]+w[i]*k);
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

多重背包问题II

链接 :

  • 5. 多重背包问题 II - AcWing题库

  • Problem - 2844

思路 :

其它均与上一题相同,不过数据范围扩大了,会超时,那么就要考虑优化的问题;

优化 (这里采用二进制优化的方法) :

dp(3) - 背包问题(上)_第8张图片

假定给出第i组的物品数量为s[i],也就是s,那么s可以表示为,1,2,4,8,... 2^k,c的和;

由1,2,4,8,... 2^k,c组合可以表示任意[1,s]之间的数;一个log(s)个

例如200可以根据上式表示为 :

1 2 4 8 16 32 64 63

这样分之后,也就是对这每一组数据的分类形成的集合做一个01背包问题,可以把朴素代码的时间复杂度O(N * V * S) 优化为 O(N * V * log(S));

可能这会理解不了,请看详细的二进制优化详解 : 背包问题的二进制优化_二进制优化很神奇_桐小目的博客-CSDN博客

代码(二进制优化) :

#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 25000;
int n,m;
int v[N],w[N],s[N];
int f[N];
int main(){
    cin>>n>>m;
    int cnt = 0;
    for(int i=1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int k = 1;
        while(k<=s){
            cnt++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if(s>0){
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=v[i];j--){
            f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
    cout << f[m] << endl;
    return 0;
}

分组背包问题

题目链接 :

9. 分组背包问题 - AcWing题库

思考 :

  • 集合表示 : f(i,j) 还是 表示 只从前i组物品中选,且总体积不大于j的所有方案数;

  • 属性 : max

  • 状态计算 : 思想与前几题相类似,f(i,j)划分为不从第i组中选物品和从第i组中选第k个物品两种情况;

  • 状态转移方程式 :

    f[i][j] = max(f[i-1][j] , f[i-1][j-v[i,k]]+w[i][k]);

那么就可以轻松得到以下代码 :

代码 (朴素):

#include 
#include 
#include 
using namespace std;
const int N = 105;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N][N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=0;j>v[i][j]>>w[i][j];
    }
    for(int i=1;i<=n;i++)
       for(int j=0;j<=m;j++){
       f[i][j] = f[i-1][j];
         for(int k=0;k=v[i][k])
             f[i][j] = max(f[i][j] , f[i-1][j-v[i][k]]+w[i][k]);
         }
       }
    cout << f[n][m] << endl;
    return 0;
}

代码(一维优化) :

因为只用到了第i-1列,所以可以仿照01背包的套路逆向枚举体积

#include
using namespace std;
​
const int N=110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m,k;
​
int main(){
    cin>>n>>m;
    for(int i=0;i>s[i];
        for(int j=0;j>v[i][j]>>w[i][j];
        }
    }
​
    for(int i=0;i=0;j--){
            for(int k=0;k=1;k--)也可以
                if(j>=v[i][k])     f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);  
            }
        }
    }
    cout< 
 

你可能感兴趣的:(acwing,算法提高课,算法学习,动态规划,算法,背包)