上次,我们讲了最基础最基础…的01背包问题,这次,我们就来讲一下关于生活中不可能出现的背包问题
无敌完全背包问题
经典问题
【例n+1】之前的旅行者回来了。他胜利归来,带来了很多很多东西。又过了几天,他的手的脚又开始痒痒了。于是,他又开始准备东西,继续去探险。他有N种不同物品,又有一个容量为V的背包,每件物品都可以无限拿,。让这位旅行者获得最大的生存几率,然后又使背包不会超限,请变出编出程序,帮助他。
提前透露下哈,这人姓鲁。至于名,自己猜吧。
基本思路
这个问题,非常不接近接近01背包问题,所不同的是每件物品有无限种!!!(天哪)从每件物品的角度去思考,不仅仅是拿和不拿,而是拿一件,两件,······(无限件) 。如果按照解01背包问题的思路,f[i][v]表示的是前i种物品放入一个容量为v的背包的最大利益。所以,按照这个思路,可以根据每种物品不同的策略写出状态转移方程,like this:
f
(
i
)
(
v
)
=
m
a
x
(
f
(
i
−
1
)
(
v
−
k
∗
w
(
i
)
)
+
k
∗
c
(
i
)
∣
0
<
k
∗
w
(
i
)
<
=
v
)
f(i)(v)=max(f(i-1)(v-k*w(i))+k*c(i)|0<k*w(i)<=v)
f(i)(v)=max(f(i−1)(v−k∗w(i))+k∗c(i)∣0<k∗w(i)<=v)
这样,根据01基础背包问题的思路就求出了这样不清晰 清晰的方法。
优化
这个算法使用一维数组的话,根据01背包问题改进版,伪代码如下:
for(i=1...N)
for(v=0...V)
f[v]=max(f[v],f[v-w[i]]+c[i]);
诶亚,这和01背包问题只是差一个V次循环的方向而已。为什么这一改就可以了呢?首先回顾下上次01背包问题为什么要进行逆序搜寻。
在这里,我们先考虑上面的思路怎样实现。肯定有一个主循环for(1…n)每次算出来二维数组f[i][0~V]的所有值。如果只用一个数组f[0…V]能不能保证第i次循环完后f[v]中表示着f[i][v]呢?实际,这要求在每次主循环中我们以v=V…0的逆序推f[v],这样能保证f[v]时f[v-w[i]]保存的是状态f[i-1][v-w[i]]的值。所以,伪代码如下:
for(i=1…N)
for(v=V…0)
f[v]=max{f[v],f[v-w[i]]+c[i]};
重点来了,“实际,这要求在每次主循环中我们以v=V…0的逆序推f[v],这样能保证f[v]时f[v-w[i]]保存的是状态f[i-1][v-w[i]]的值”。于是,我们就能看出,这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-w[i]]推导而来。换句话说,这正是为了保证每件物品只能选择1次,保证在考虑“选入第i件物品”这个策略时,依据的是一个不是已经放入了第i件物品的子结果f[i-1][v-w[i]]。
看得懂吗? 我似乎也看不懂。。。
让我们简单来说明吧。状态表:设有3件物品,价值分别为1,2,3,重量分别为2,3,1,背包重量为5。这时,从01背包思想考虑,双逆推产生的状态表:
i | 3 | 2 | 1 |
---|---|---|---|
v | 5,3 | 5,3,2,0 | 5,3,2,2,1,0 |
f | 0,1 | 0,1,2,5 | 0,1,2,4,5,3 |
f[v]=max{f[v],f[v-w[i]]+c[i]} 在双逆推中,f[v]不可能是由f[v]本身推出。
逆推+顺推
i | 1 | 2 | 3 |
---|---|---|---|
v | 1,3,5 //此处1,3,5都推出,向右看: | 0,2 | 0 //此处的0是由两个1号+1个3号得来 ,0,1,1,2,2,3,4,5 |
f | 1,0 |
f[v]=max{f[v],f[v-w[i]]+c[i]}在单顺单逆中,f[v]可能是由f[v]本身推出。
这下看懂了吧。
继续优化
根据上次for优化经验,优化v=V…w[i],同样,没得放了,你还放个啥?
伪代码如下:
for(i=1...N)
for(v=w[i]...V)
f[v]=max(f[v],f[v-w[i]]+c[i]);
经过了这复杂难懂的思考,最后的代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
int thing[1000001][3]; //如有数据内容不懂,请看上篇文章
int max(int a,int b)
{
return a>b? a:b;
}
int main()
{
int n,V;
cin>>V>>n;
for(int i=1;i<=n;i++) cin>>thing[i][0]>>thing[i][1];
for(int i=1;i<=n;i++)
for(int v=thing[i][0];v<=V;v++)
{
thing[v][2]=max(thing[v][2],thing[v-thing[i][0]][2]+thing[i][1]);
}
cout<<thing[V][2];
return 0;
}
欸哟,写一篇文章真累真舒服啊,请大家多多关注我的文章,有什么问题可以多多提出。