背包问题

Posted dolires

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了背包问题相关的知识,希望对你有一定的参考价值。

复健\\(Day1\\)

今天复习基础背包问题,在\\(ACWing\\)上使用挑战模式去打模板,提高打代码速度

\\(01\\)背包

解决每种物品只有一样的情况
时间复杂度\\(O(nV)\\),空间复杂度优化后为\\(O(V))\\)
空间优化的代码中体积一维从后往前更新,因为其递推公式为\\(dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])\\),若我们从前往后更新那么相当于改变了\\(dp[i-1][j-v[i]]\\)的值,这样会对后面的更新造成影响

#include<iostream>
#include<cstdio>
#define maxn 1010
using namespace std;

int dp[maxn],v[maxn],w[maxn];
int n,V;

int main()

    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=V;j>=v[i];j--) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    
    printf("%d\\n",dp[V]);
    return 0;

多重背包

解决每种物品有确定值的情况

二进制优化

进行二进制优化后,时间复杂度由\\(O(nVs)\\)优化为\\(O(nVlogs)\\)
之所以能进行二进制优化的原因:对于某种数量\\(s\\),我们不需要从\\(0\\)开始枚举其选择的数量,而是采用分组打包的方式\\(:2\\ ^0,2\\ ^1,...2\\ ^k\\),直至\\(s-2\\ ^k+1\\leqslant 0\\)时,无法再用\\(2\\ ^t\\)的形式表示,那么最后一组就为\\(s-2\\ ^k+1\\)个,这样我们的枚举数就从原本的\\(s\\)优化为了\\(logs\\)
体积一维从后往前更新,因为其递推公式为\\(dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])\\)

#include<iostream>
#include<cstdio>
#define maxn 2010
using namespace std;


int dp[maxn],v[maxn],w[maxn],s[maxn],nv[maxn],nw[maxn],cnt;
int n,V;

int main()

    cin>>n>>V;
    for(int i=1;i<=n;i++)
    
        cin>>v[i]>>w[i]>>s[i];
        int k;
        for(k=1;s[i]-(1<<k)+1>=0;k++)
        
            nv[++cnt]=v[i]*(1<<(k-1));
            nw[cnt]=w[i]*(1<<(k-1));
        
        k--;
        nv[++cnt]=v[i]*(s[i]-(1<<k));
        nw[cnt]=w[i]*(s[i]-(1<<k));
    
    for(int i=1;i<=cnt;i++)
    
        for(int j=V;j>=nv[i];j--) dp[j]=max(dp[j],dp[j-nv[i]]+nw[i]);
    
    printf("%d\\n",dp[V]);
    return 0;

优先队列优化

https://www.acwing.com/solution/content/53507/
上面是写的很清楚的解析
滚动数组的长度为\\(s[i]+1\\),所有满足\\(j\\equiv r(modv[i])\\)进行维护从而得出最大值
最后时间复杂度为\\(O(nV)\\),空间复杂度为\\(O(v)\\)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int N=1010,M=20010;

int g[M],f[M],q[M];
int v[N],w[N],s[N];
int n,V;

int main()

    cin>>n>>V;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++)
    
        memcpy(g,f,sizeof(g));
        for(int r=0;r<v[i];r++)
        
            int hh=0,tt=-1;//每次都是新的队列
            for(int j=r;j<=V;j+=v[i])
            
                while(hh<=tt&&j-q[hh]>s[i]*v[i]) hh++;//最开头那个f值最大的v,我们的j减去它之后剩余的体积过大,其实没必要存,所以出队
                while(hh<=tt&&g[q[tt]]+(j-q[tt])/v[i]*w[i]<=g[j]) tt--;//最后的那个元素并不比g[j]优,无法更新别的元素,所以出队
                q[++tt]=j;
                f[j]=g[q[hh]]+(j-q[hh])/v[i]*w[i];//更新最前端的那个元素
            
        
    
    printf("%d\\n",f[V]);
    return 0;

完全背包

解决物品数量为无穷多个的情况
时间复杂度为\\(O(nV)\\),空间复杂度优化为\\(O(V)\\)
它的体积一维从前往后更新,原因是递推公式为\\(dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])\\)它是由\\(dp[i][j-v[i]]+w[i]\\)更新而来,所以我们要先更新小的\\(j\\),这样才能进一步更新后面的\\(j\\)

#include<iostream>
#include<cstdio>
#define maxn 2010
using namespace std;

int dp[maxn],v[maxn],w[maxn];
int n,V;

int main()

    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=v[i];j<=V;j++) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    
    printf("%d\\n",dp[V]);
    return 0;

混合背包

很简单的,这种情况就是判断此种物品是有一个还是有确定个,还是有无穷多个,使用不同处理即可

#include<iostream>
#include<cstdio>
#define maxn 1010
using namespace std;

int dp[maxn],v[maxn],w[maxn],s[maxn],nv[maxn],nw[maxn],cnt;
int n,V;

int main()

    cin>>n>>V;
    for(int i=1;i<=n;i++)
    
        cin>>v[i]>>w[i]>>s[i];
        if(s[i]==-1)
        
            for(int j=V;j>=v[i];j--) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        
        else if(s[i]==0)
        
            for(int j=v[i];j<=V;j++) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        
        else
        
            int k;
            cnt=0;
            for(k=1;s[i]-(1<<k)+1>0;k++)
            
                nv[++cnt]=v[i]*(1<<(k-1));
                nw[cnt]=w[i]*(1<<(k-1));
            
            k--;
            nv[++cnt]=v[i]*(s[i]-(1<<k)+1);
            nw[cnt]=w[i]*(s[i]-(1<<k)+1);
            for(int j=1;j<=cnt;j++)
            
                for(int m=V;m>=nv[j];m--) dp[m]=max(dp[m],dp[m-nv[j]]+nw[j]);
            
        
    
    printf("%d\\n",dp[V]);
    return 0;

动态规划——背包问题python实现(01背包完全背包多重背包)

参考:

背包九讲——哔哩哔哩

背包九讲



01背包问题

01背包问题

描述:

有N件物品和一个容量为V的背包。

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

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


二维动态规划

f[i][j] 表示只看前i个物品,总体积是j的情况下,总价值最大是多少。 result = max(f[n][0~V]) f[i][j]:

  • 不选第i个物品:f[i][j] = f[i-1][j];

  • 选第i个物品:f[i][j] = f[i-1][j-v[i]] + w[i](v[i]是第i个物品的体积) 两者之间取最大。 初始化:f[0][0] = 0 (啥都不选的情况,不管容量是多少,都是0?)

代码如下:

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

# 初始化,先全部赋值为0,这样至少体积为0或者不选任何物品的时候是满足要求  
dp = [[0 for i in range(v+1)] for j in range(n+1)]

for i in range(1, n+1):
    for j in range(1,v+1):
        dp[i][j] = dp[i-1][j]  # 第i个物品不选
        if j>=goods[i-1][0]:# 判断背包容量是不是大于第i件物品的体积
            # 在选和不选的情况中选出最大值
            dp[i][j] = max(dp[i][j], dp[i-1][j-goods[i-1][0]]+goods[i-1][1])


print(dp[-1][-1])


一维动态优化

从上面二维的情况来看,f[i] 只与f[i-1]相关,因此只用使用一个一维数组[0~v]来存储前一个状态。那么如何来实现呢?

第一个问题:状态转移

假设dp数组存储了上一个状态,那么应该有:

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

max函数里面的dp[i]代表的是上一个状态的值。

第二个问题:初始化

这里开始初始化一个长度为V+1一维数组,选取0个物品时,体积为0~V时的最大价值(值全部为0)。

第三个问题:递推关系

试想一下,我要保证求取第i个状态时,用到一维数组中的值是第i-1个状态的。如果,我从前往后推,那么当我遍历到后面时,我用到的状态就是第i个状态而不是第i-1个状态。比如:

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

这里的dp[i-v[i]]是已经重新赋值的,而不是上一个状态的值,所以这样是错误的。因此,我们要从后往前推

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

dp = [0 for i in range(v+1)]

for i in range(n):
    for j in range(v,-1,-1): # 从后往前
        if j >= goods[i][0]:
            dp[j] = max(dp[j], dp[j-goods[i][0]] + goods[i][1])

print(dp[-1])


确定体积的情况

如果我要求的不是尽可能最大的价值,而是刚好等于背包容量的最大价值,那么该如何去做呢?


完全背包问题

完全背包问题

描述:

有N件物品和一个容量为V的背包,每件物品都有无限个!

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

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


一维动态规划

完全背包问题跟01背包问题最大的区别就是每一个物品可以选无数次,因此当我们考虑到第i个物品时,我们应该考虑的情况是:不选这个物品、选一次这个物品、选两次这个物品......,直到不能再选(选的次数k,k*v[i] > j,j为当前背包容量),然后再从这些情况中选最大的。代码如下:

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])
dp = [0 for i in range(v+1)]
for i in range(n):
    for j in range(v,-1,-1): # 从后往前
        k = j//goods[i][0]  # 能选多少次
        # 从这些次里面取最大
        dp[j] = max([dp[j- x* goods[i][0]] + x * goods[i][1] for x in range(k+1)])

print(dp[-1])


一维动态规划(优化)

刚刚那个问题,我们是延续01背包的问题,从后往前递推。但是对于这个问题,其实可以通过从前往后递推。如何理解呢?

假设在考虑第i个物品时的两个状态:

A:dp[k*v[i] + x]

B:dp[(k-1)*v[i] + x]

根据前面的归纳,从前一个状态递推过来:

  • 要求A的值,应该要从k+1个状态中选出最大的:

    dp[x] + k*w[i]
    dp[v[i] + x] + (k-1)*w[i]
    dp[2*v[i] + x] + (k-2)*w[i]
    ...
    ...
    dp[(k-1)*v[i] + x] + w[i]
    dp[k*v[i] + x
  • 要求B的值,应该要从k个状态中选出最大的:

    dp[x] + (k-1)*w[i]
    dp[v[i] + x] + (k-2)*w[i]
    dp[2*v[i] + x] + (k-3)*w[i]
    ...
    ...
    dp[(k-2)*v[i] + x] + w[i]
    dp[(k-1)*v[i] + x

我们可以看到,一一对应过来的话,这两个状态实际上只差一个w[i]的值。因此:

一方面我们可以根据前一个状态(i-1)推出此时的状态,另一方面由于当前状态前面的值也是当前问题的子问题,因此我们也可以从前面的值推到后面的值。

从前往后递推的代码如下:

n, v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])
dp = [0 for i in range(v+1)]
for i in range(n):
    for j in range(v+1):
        if j >= goods[i][0]:
            dp[j] = max(dp[j], dp[j-goods[i][0]] + goods[i][1])

print(dp[-1])

多重背包问题

多重背包问题

描述:

有N件物品和一个容量为V的背包。

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

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


一维动态规划

其实跟上面的完全背包问题类似,只不过我们从后往前递推的时候,物体i选取的次数应该要重新考虑下:

k = min(s[i], j//v[i]), j为当前的背包容量

代码如下:

n,v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

dp = [0 for i in range(v+1)]

for i in range(n):
    for j in range(v, -1, -1):
        # 考虑两种情况的最小值
        k = min(j//goods[i][0], goods[i][2])
        dp[j] = max([dp[j-x*goods[i][0]] + x*goods[i][1] for x in range(k+1)])

print(dp[-1])


一维动态规划(转换01背包)

想法很简单,直接把背包中的物品展开,展成很多数量为1的物品,这样就转换为01背包问题。代码如下:

n,v = map(int, input().split())
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])

new_goods = []

# 展开
for i in range(n):
    for j in range(goods[i][2]):
        new_goods.append(goods[i][0:2])

goods = new_goods
n = len(goods)

# 01背包问题
dp = [0 for i in range(v+1)]

for i in range(n):
    for j in range(v,-1,-1):
        if j>= goods[i][0]:
            dp[j] = max(dp[j], dp[j - goods[i][0]] + goods[i][1])

print(dp[-1])

以上是关于背包问题的主要内容,如果未能解决你的问题,请参考以下文章

背包问题

动态规划第五篇:01背包问题和完全背包问题

动态规划第五篇:01背包问题和完全背包问题

动态规划/背包问题背包问题第一阶段最终章:混合背包问题

动态规划/背包问题背包问题第一阶段最终章:混合背包问题

背包问题(01背包)