混合揹包
Time Limit : 3000/1000ms (Java/Other) Memory Limit : 65535/32768K (Java/Other)
Total Submission(s) : 10 Accepted Submission(s) : 4
Font: Times New Roman | Verdana | Georgia
Font Size: ← →
Problem Description
Input
第2..N+1行:每行三個整數Wi,Ci,Pi,前兩個整數分別表示每個物品的重量,價值,第三個整數若爲0,則說明此物品可以購買無數件,若爲其他數字,則爲此物品可購買的最多件數(Pi)。
Output
Sample Input
10 3 2 1 0 3 3 1 4 5 4
Sample Output
11 Hint 選第一件物品1件和第三件物品2件。
如果將P01、P02、P03混合起來。也就是說,有的物品只可以取一次(01揹包),有的物品可以取無限次(完全揹包),有的物品可以取的次數有一個上限(多重揹包)。應該怎麼求解呢?
01揹包與完全揹包的混合
考慮到在P01和P02中給出的僞代碼只有一處不同,故如果只有兩類物品:一類物品只能取一次,另一類物品可以取無限次,那麼只需在對每個物品應用轉移方程時,根據物品的類別選用順序或逆序的循環即可,複雜度是O(VN)。僞代碼如下:
for i=1..N
if 第i件物品是01揹包
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
else if 第i件物品是完全揹包
for v=0..V
f[v]=max{f[v],f[v-c[i]]+w[i]};
再加上多重揹包
如果再加上有的物品最多可以取有限次,那麼原則上也可以給出O(VN)的解法:遇到多重揹包類型的物品用單調隊列解即可。但如果不考慮超過NOIP範圍的算法的話,用P03中將每個這類物品分成O(log n[i])個01揹包的物品的方法也已經很優了。
當然,更清晰的寫法是調用我們前面給出的三個相關過程。
for i=1..N
if 第i件物品是01揹包
ZeroOnePack(c[i],w[i])
else if 第i件物品是完全揹包
CompletePack(c[i],w[i])
else if 第i件物品是多重揹包
MultiplePack(c[i],w[i],n[i])
在最初寫出這三個過程的時候,可能完全沒有想到它們會在這裏混合應用。我想這體現了編程中抽象的威力。如果你一直就是以這種“抽象出過程”的方式寫每一類揹包問題的,也非常清楚它們的實現中細微的不同,那麼在遇到混合三種揹包問題的題目時,一定能很快想到上面簡潔的解法,對嗎?
- #include <iostream>
- #include <string.h>
- #include <stdio.h>
- using namespace std;
- struct ss
- {
- int wi;//重量
- int ci;//價值
- int pi;//個數
- } dp[300];
- int f[300];
- int main ()
- {
- int V,N;
- while(scanf("%d%d",&V,&N)!=EOF)
- {
- memset(f,0,sizeof(f));
- for(int i=1; i<=N; i++)
- scanf("%d%d%d",&dp[i].wi,&dp[i].ci,&dp[i].pi);
- for(int i=1; i<=N; i++)
- {
- if(dp[i].pi==1)//01揹包
- {
- for(int j=V; j>=dp[i].wi; j--)
- {
- f[j]=max(f[j-dp[i].wi]+dp[i].ci,f[j]);
- }
- }
- else if(dp[i].pi==0)//完全揹包
- {
- for(int j=dp[i].wi; j<=V; j++)
- {
- f[j]=max(f[j-dp[i].wi]+dp[i].ci,f[j]);
- }
- }
- else//多重揹包
- {
- for(int j=1; j<=dp[i].pi; j++)
- {
- for(int k=V; k-j*dp[i].wi>=0; k--)
- {
- if(f[k-dp[i].wi]+dp[i].ci>f[k])
- f[k]=f[k-dp[i].wi]+dp[i].ci;
- }
- }
- }
- }
- printf("%d\n",f[V]);
- }
- return 0;
- }