背包问题1 --完全背包

完全背包问题简介

完全背包简单解释就是:

往一个固定体积的背包中放k种东西,每个东西放进背包的数量没有任何限制,每种东西放入背包都会有一定的奖励,求解如何放东西,才能使得获得的奖励总和最大。

题目链接

3. 完全背包问题 - AcWing题库icon-default.png?t=N7T8https://www.acwing.com/problem/content/3/

解决思路

对于这类问题,解决方法就是典型的动态规划。

重要的点在于推导出公式。

对于这个背包而言,每次面对一种物品,其获得的奖励范围为:

不放,放1个,放2个,放3个 直到背包装不下。

所以我们可以得到

dp[n][m]=max(dp[n-1][m],dp[n-1][m-v[n]]+w[n],.....);

dp[i][j]的含义为取到第i个东西时,背包容量还剩 j 时,所获取到的最大的奖励值。

所以就有了以下代码。

①最朴素的求解

for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            for(int t=0;t<=j/v[i];t++)
                dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]*t]+w[i]*t);//这里是选出多个里面的最大值,注意比较大小的对象
        }
        
    }

②时间复杂度降低的改进 

递归公式的推导改进  时间复杂度为n*v
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-v[i]*2]+w[i]*2 ...)
            dp[i][j-v[i]]=max(dp[i-1][j-v[i]],dp[i-1][j-v[i]*2]+w[i] ...);

综合对比:

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


    for(int i=1;i<=n;i++){
        for(int j=1;j<=k;j++){
            if(j<v[i]) dp[i][j]=dp[i-1][j];
            else{
                dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]);//核心代码优化
            }
        }
    }

 ③空间复杂度改进

#include<iostream>
#include<cstring>
using namespace std;
const int N=1005;
int dp[N][N];
int f[N];
int v[N],w[N];
int main(){
    int n,k;//物品种数和背包体积
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>w[i];
    }
    memset(dp,0,sizeof(dp));
    
    //为了更加简化空间复杂度  如果不用输出具体的选择方案,只需要单纯的求取最大值 那么可以将数组转变为一维
    //dp[i][j]的值仅仅与依赖i-1时的状态,因此,代码中无需使用二维的dp[i][j]进行存储,使用一维的f[j]进行存储,减少程序的空间。
    //继续分析问题,因为dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]); f[j]应该从f[j-v[i],也就是比他小的数组值中获取,要与01背包区别开来 
    for(int i=1;i<=n;i++){
        for(int j=v[i];j<=k;j++){//这个顺序必须是这样的原因见上
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[k];
    //cout<<dp[n][k];
    return 0;
}

一个变型题---可以用于练手

题目链接

900. 整数划分 - AcWing题库icon-default.png?t=N7T8https://www.acwing.com/problem/content/902/

求解思路

这个问题最开始我陷入了思维误区,我一直在想怎么保证获得的数字划分一定是按照降序得到的。

后来查阅一些资料,提到了可以用完全背包的思路去求解,豁然开朗。

相当于我只要把体积为 0-n 的物品塞入容量为n的背包中,保证背包刚好被塞满,求解一共有多少种方法即可。不需要考虑顺序问题,只要在取之后,将数排下顺序即可。

求解代码

#include<iostream>
#include<cstring>
using namespace std;
const int N=1005;
const int mod=1e9+7;
int f[N]
int dp[N][N];//dp[i][j]代表的物理含义为:在前i个物品中取到总体积刚好满足容量的划分数
int main(){
    int n;
    cin>>n;
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=n;i++){
        dp[i][0]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=n;j++){
            if(i>j) dp[i][j]=dp[i-1][j]%mod;//当前物品重量大于剩余容量,则该物品不能取了
            else
            dp[i][j]=(dp[i-1][j]+dp[i][j-i])%mod;
            //dp[i][j]=dp[i-1][j]+dp[i][j-i]+dp[i][j-2*i]+...  等于取0个,取1个和取2个的和
            //dp[i][j-i]=dp[i-1][j-i]+dp[i][j-2*i]+dp[i][j-3*i]+...
            //dp[i][j]=dp[i-1][j]+dp[i][j-i];
        }
    }
//一维改进
      f[0]=1;
    /*for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            f[j]=(f[j-i]+f[j])%mod;
        }
    }*/
    cout<<dp[n][n];
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值