上次,我們講了最基礎最基礎…的01揹包問題,這次,我們就來講一下關於生活中不可能出現的揹包問題
無敵完全揹包問題
經典問題
【例n+1】之前的旅行者回來了。他勝利歸來,帶來了很多很多東西。又過了幾天,他的手的腳又開始癢癢了。於是,他又開始準備東西,繼續去探險。他有N種不同物品,又有一個容量爲V的揹包,每件物品都可以無限拿,。讓這位旅行者獲得最大的生存機率,然後又使揹包不會超限,請變出編出程序,幫助他。
提前透露下哈,這人姓魯。至於名,自己猜吧。
基本思路
這個問題,非常不接近接近01揹包問題,所不同的是每件物品有無限種!!!(天哪)從每件物品的角度去思考,不僅僅是拿和不拿,而是拿一件,兩件,······(無限件) 。如果按照解01揹包問題的思路,f[i][v]表示的是前i種物品放入一個容量爲v的揹包的最大利益。所以,按照這個思路,可以根據每種物品不同的策略寫出狀態轉移方程,like this:
這樣,根據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;
}
欸喲,寫一篇文章真累真舒服啊,請大家多多關注我的文章,有什麼問題可以多多提出。