1. 程式人生 > >揹包九講——多重揹包

揹包九講——多重揹包

多重揹包是完全揹包的升級版,是《揹包問題九講》裡的第三講,先來看看《揹包問題九講》是怎麼表述這個問題的:

題目
有N種物品和一個容量為V的揹包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大

所屬專欄:戳我訪問
再來看看揹包問題九講是如何解決這個問題的:

基本演算法
這題目和完全揹包問題很類似。基本的方程只需將完全揹包問題的方程略微一改即可,因為對於第i種物品有n[i]+1種策略:取0件,取1件……取n[i]件。令f[i][v]表示前i種物品恰放入一個容量為v的揹包的最大權值,則有狀態轉移方程:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
複雜度是O

(VΣn[i])

呃呃呃,由於此演算法時間複雜度較高,故不給解釋喝程式碼。

然後就是把這個問題轉化01揹包:

轉化為01揹包問題
另一種好想好寫的基本方法是轉化為01揹包求解:把第i種物品換成n[i]件01揹包中的物品,則得到了物品數為Σn[i]的01揹包問題,直接求解,複雜度仍然是O(V*Σn[i])。
但是我們期望將它轉化為01揹包問題之後能夠像完全揹包一樣降低複雜度。仍然考慮二進位制的思想,我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價於取若干件代換以後的物品。另外,取超過n[i]件的策略必不能出現。
方法是:將第i種物品分成若干件物品,其中每件物品有一個係數,這件物品的費用和價值均是原來的費用和價值乘以這個係數。使這些係數分別為1,2,4,…,2^(k-1),n[i]-2^k+1,且k是滿足n[i]-2^k+1>0的最大整數。例如,如果n[i]為13,就將這種物品分成係數分別為1,2,4,6的四件物品。
分成的這幾件物品的係數和為n[i],表明不可能取多於n[i]件的第i種物品。另外這種方法也能保證對於0..n[i]間的每一個整數,均可以用若干個係數的和表示,這個證明可以分0..2^k-1和2^k..n[i]兩段來分別討論得出,並不難,希望你自己思考嘗試一下。
這樣就將第i種物品分成了O(log n[i])種物品,將原問題轉化為了複雜度為O(V*Σlog n[i])的01揹包問題,是很大的改進。
下面給出O(log amount)時間處理一件多重揹包中物品的過程,其中amount表示物品的數量:

procedure MultiplePack(cost,weight,amount)
    if cost*amount>=V
        CompletePack(cost,weight)
        return
    integer k=1
    while k<amount
        ZeroOnePack(k*cost,k*weight)
        amount=amount-k
        k=k*2
    ZeroOnePack(amount*cost,amount*weight)

希望你仔細體會這個虛擬碼,如果不太理解的話,不妨翻譯成程式程式碼以後,單步執行幾次,或者頭腦加紙筆模擬一下,也許就會慢慢理解了。

這才是我想講得重點,轉化成01揹包相信大家都會,這裡不做過多的講解,重點是轉化成01揹包的2進位制優化,二進位制優化就有那麼一點點倍增的思想了(一點點而已),因為10進位制的任何一個整數都是可以被二進位制表達的,所以這個而二進位制裡的每一位都是2的幾次方,所以只需要把物品的數量分成2的1~k次方就可以了,其中k為2的k次方小於n[i]的最大值。
程式碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>

int c[1001],w[1001],f[1001];
int main()
{
    int n = 0,v,x;
    std::cin>>x>>v;
    for(int i = 0;i<x;i++)
    {
        int a,b,s,k = 1;
        std::cin>>a>>b>>s;
        while(s-k>=0)
        {
            c[n++] = a*k;w[n-1] = b*k;
            s-=k;k*=2;
        }
        c[n++] = s*a;w[n-1] = s*b;
    }
    for(int i = 0;i<n;i++)
        for(int j = v;j>=c[i];j--)
            f[j] = std::max(f[j],f[j-c[i]]+w[i]);
    std::cout<<f[v];
    return 0;
}