背包问题总结

背包问题,最经典的dp
首先动态规划问题,我们需要考虑的有两点,1是状态表示,2是状态计算。
状态表示f[i,j]:首先是一个集合,只考虑前i个物品,且总体积不大于j的所有选法,然后集合的属性这里以最大值表示
状态计算:集合的划分,也就是状态转移方程的求解

01背包

顾名思义,01的意思是指每个物品可以选择的次数为0或者1,选到第i个物品的时候,就可以往前推选择第i-1个物品的情况,如果第i个物品没有选,那么选择第i-1个物品的时候体积也是j,也就是f[i][j]=f[i-1][j],如果第i个物品选了,那么第i-1个物品的时候体积就是j-v[i],即f[i][j]=f[i-1][j-v[i]],需要求的是最大值,只需要在这两者间取最大的一个即可

朴素的代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int v[N],w[N];
int dp[N][N];
int 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;
}

滚动数组优化

可以看出当选择到第i个物品时,只与选第i-1个时有关
那么是否可以直接删除掉第一维呢
删除后的转移方程为:dp[j]=max(dp[j],dp[j-v[i]]+w[i])显然是不对的,
从二维的可以看出来,第i-1个物品选择时的体积一定小于等于第i次选择的,
如果我们写成一维的情况仍然按照体积从小到大的话,前边的数据会提前发生改变,影响后边的数据
除此之外,当j小于v[i]时,dp[j]=dp[j]很明显是一句废话(
所以应该修改为

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]);
        }
    }

完全背包

完全的意思就是指每个物品可以选择的次数是无数次
当选择到第i个物品时,i可以选择的次数很多种,假设为k,那么选择第i-1个物品时候的体积就是j-kv[i],方程为dp[i][j]=dp[i-1][j-kv[i]]+k*w[i]

朴素代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N];
int dp[N][N];
int 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++)
        {
            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;
}

时间复杂度达到了1e9,果不其然TLE,接下来就是考虑如何优化

dp[i,j]=max(dp[i-1,j],dp[i-1,j-v[i]]+w[i],dp[i-1,j-2*v[i]]+2*w[i]........)
dp[i,j-v[i]]=max(     dp[i-1,j-v[i]]      dp[i-1,j-2*v[i]+w[i]]..........)
dp[i,j-2v[i]]=max(                        dp[i-1,j-2*v[i]],dp[i-1,j-3v[i]]+w[i]    
....
...
...
...
依次类推,每一项的最大值都是dp[i-1,j]与其后边一项的最大值加w[i]比较取最大值

然后得到以下优化,仍然是使用滚动数组

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N];
int dp[N];
int 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=v[i];j<=m;j++)
    dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[m]<<endl;
    return 0;
}

多重背包

与完全背包的区别在于,每个物品有一定的数量

朴素代码

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int v[N],w[N],s[N];
int dp[N][N];
int n,m;
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++)
            dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k);
        }
    }
    cout<<dp[n][m]<<endl;
    return 0;
}

优化为01背包

复杂度由On3变为On2logn
二进制下的转化,某个物品有m个,那么m可以进行一下拆分
1 2 4 8 …即把m拆开,拆成这些数字相加,使这些数字相加得到的结果是m
可以证1-m之间的数字均可以由这些数字组成,(此处不做证明
所以优化之后变成了01背包

#include<bits/stdc++.h>
using namespace std;
const int N=25000;
int v[N],w[N];
int dp[N];
int n,m;
int a,b,c; //a是体积,b是价值,c是个数
int main()
{
    cin>>n>>m;
    int cnt=1;
    for(int i=0;i<n;i++)
    {
        cin>>a>>b>>c;
        int res=1;
        while(res<=c)
        {
            v[cnt]=res*a;
            w[cnt]=res*b;
            cnt++;
            c-=res;
            res*=2;
        }
        if(c!=0)
        {
            v[cnt]=c*a;
            w[cnt]=c*b;
            cnt++;
        }
    }
    for(int i=1;i<cnt;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;
}

单调队列进行优化的还没看,之后补上(

分组背包

分组背包感觉写起来和多重背包有一点点像,在某一组中,可以选择的情况有不选,从第1到s[i],都有可能

代码

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int s[N];
int v[N][N];
int w[N][N];
int dp[N][N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        for(int j=1;j<=s[i];j++)
        cin>>v[i][j]>>w[i][j];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
             dp[i][j]=dp[i-1][j];
            for(int k=1;k<=s[i];k++)
            {
                if(j>=v[i][k])
                dp[i][j]=max(dp[i][j],dp[i-1][j-v[i][k]]+w[i][k]);
            }
        }
    }
    cout<<dp[n][m]<<endl;
    return 0;
}

(今日分享就到这里,剩下的之后补齐
制作不易,感谢观看

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值