一维费用的背包问题(01,多重,完全等)

做题时发现,长时间不接触,基本的背包问题都只能用O(V^2)复杂度来暴力做了,简直绝望。。。

一、0-1背包

给定背包容量V,N件物体,放入第i件物体花费为C[i],价值为W[i],求解背包最多能放多少价值的东西。

每种物体有两种选择——放或不放,我觉得我想到了一种时间复杂度O(2^n)的“优秀”算法~~

当然,n稍微大一点,2^n的时间就很让人绝望了,所以,这里要说的是时间复杂度为O(V*N)的算法

首先设dp[i][v]表示前i种物体放入容量为v的背包能获得的最大价值。

那么DP方程:dp[i][j] = max( dp [i-1] [j], dp[i-1][ j-C[i]] + W[i])

当讨论第i个物体时,dp[i-1][j] 表示第i个物体不塞进背包,dp[i-1][j-C[i]] + W[i] 表示第i个物体塞进背包

代码:

for(int i = 0; i < N; ++ i)
    for(int j = C[i]; j <= V; ++ j)
        dp[i][j] = max(dp[i-1][j], dp[i-1][j-C[i]]+W[i]);

嗯,时间复杂度O(V*N),完美~

发现一个问题,我们求第dp[i][*]的时候是由dp[i-1][*]转移过来的,而算完dp[i][*]之后,dp[i-1][*]不再起作用,也就是说dp[i-1][*]只在求dp[i][*]的时候使用了,使用完内存其实是可以释放掉了。

也就是应该优化成这样:

for(int i = 0; i < N; ++ i)
    for(int j = C[i]; j <= V; ++ j)
        dp[j] = max(dp[j], dp[j-C[i]]+W[i]);

这样降低一维,貌似更加完美,但是这里的dp[j-C[i]]在计算dp[j]之前已经更新了,也就是说,这里的dp[j-C[i]]其实是之前的dp[i][j-C[i]],而并非dp[i-1][j-C[i]]。也就是说,我们需要先计算dp[j],后计算dp[j-C[i]],如何操作呢?

额,见下面:

for(int i = 0; i < N; ++ i)
    for(int j = V; j >= C[i]; -- j)
        dp[j] = max(dp[j], dp[j-C[i]]+W[i]);

空间复杂度有O(V*N)优化成了O(V+N),O(V)表示dp[]数组,O(N)表示C[]数组和W[]数组。完美~

空间有时候可以进一步再优化为O(V),这里就不提了,用到了所谓的滚动数组~

 

二、多重背包

有N种物品和一个容量为V的背包。第i种物品最多有M[i]件可用,每件耗费的空间是C[i],价值是W[i]。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

和0-1背包类似,但多重背包的每一种物体数量可以不是1

那么我们就可以把多重背包拆成0-1背包,时间复杂度O(V*∑M[i])。

然而,还可以优化一波,我们知道,当你拥有1,2,4,8,16......这些数时(每个数只有一个),你可以用它们中某一些的和表示任何数(什么?你不知道这个,那换个说法,2^0,2^1,2^2,2^3,2^4,……可以表示任何数字,还没明白的话,就去看看一个二进制数是怎么表示的。。。),也就是说,其实我们可以把M[i]拆成1,2,4,8,.....,2^(k-1),M-2^k+1,其中k=(floor)log M[i]。这样可以表示出第i种物品取任意不超过M[i]个。时间复杂度O(V*∑log M[i]),耐斯~~

江湖传说,还有一种O(V*N)的方法,然而我不会啊。。。

 

三、完全背包

有N种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i种物品的耗费的空间是Ci,得到的价值是Wi。求解:将哪些物品装入背包,可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

额,可以转化为多重背包——虽然物品无限,但是容量有限,也就是说第i件物品最多不超过V/C[i]个。

这里要讲的是O(V*N)的算法~

for(int i = 0; i < N; ++ i)
    for(int v = C[i]; v <= V; ++ v)
        dp[v] = max(dp[v], dp[v-C[i]]+W[i]);

如果理解了0-1背包第二重循环为什么要反着写,那么理解这段代码应该不是很难~

当初反的写,不就是因为每种物体只有1个,正的循环过去使得这个物体被拿了很多次了(前面管它叫作“已经更新了”,所谓“更新”不就是把它塞进了背包吗?),破坏了唯一性。这样我们就不难理解完全背包为什么这么写了——每种物品数量无限啊!!

 

四、混合背包

如果说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?

这里举个0-1混合完全背包的例子:

for(int i = 0; i < N; ++ i)
    if 第i种物品有无限个(完全背包)
        for(int v = C[i]; v <= V; ++ v)
            dp[v] = max(dp[v], dp[v-C[i]]+W[i]);
    else if 第i种物品只有1个(0-1背包)
        for(int v = V; v >= C[i]; -- v)
            dp[v] = max(dp[v], dp[v-C[i]]+W[i]);

我觉得这一个例子已经代表一切了,就是这么简单粗暴~

 

五、关于初始化的问题

背包必须装满和不一定装满,dp数组的初始化显然是不一样的

简单地说:

必须装满,dp[0]=0,其他都是-INF,表示未定义状态,也就是说,你必须把这个背包从0装到满,假设没有一个花费为1的物品,但有一个花费为3的物体和俩花费为2的物体,那么dp[4]只能从dp[0]转移到dp[2]再转移到dp[4]而不能从dp[1]转移到dp[4]——因为没有一种方法可以达到dp[1]啊,dp[1]并不存在的啊

不一定装满,dp[]全0,还是和上面一样的假设,dp[4]是可以从dp[1]转移过来的,虽然我们不能达到“背包容量花费了1”这个状态,但是dp[1]转移到dp[4]不就相当于装了一个4-1=3的背包吗(因为背包里是否有东西,这个花费为3的物品的价值是不变的,所以从dp[1]转移到dp[4]和dp[0]转移到dp[3]带来的总价值是不变的。)?既然V=4是不需要装满的,那么这样显然是成立的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值