1. 程式人生 > 實用技巧 >演算法筆記----揹包九講 ③多重揹包問題

演算法筆記----揹包九講 ③多重揹包問題

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

void completePack(int dp[],int value,int weight,int total)
{
    int i;
    for(i=weight;i<=total;i++)
    {
        dp[i]=max(dp[i],dp[i-weight]+value);
    }
}

void ZeroOnePack(int dp[],int value,int weight,int total)
{
    int i;
    for(i=total;i>=weight;i--)
    {
        dp[i]=max(dp[i],dp[i-weight]+value);
    }
}


//多重揹包問題 優化 一維陣列 二進位制的思想  時間複雜度為O(V*Σlog n[i])
void mutiPack(int dp[],int value,int weight,int amount,int total)
{
    if(weight*amount>total)
    {
        completePack(dp,value,weight,total);
    }
    else
    {
        int k=1;
        while(amount-k>=0)
        {
            ZeroOnePack(dp,k*value,k*weight,total);
            amount-=k;
            k*=2;
        }
        ZeroOnePack(dp,amount*value,amount*weight,total);
    }
}


int main()
{
    int n,w;
    cin>>n>>w;
    int i;
    int wi,vi,ci;
    for(i=0;i<n;i++)
    {
        cin>>wi>>vi>>ci;
        mutiPack(dp_1,vi,wi,ci,w);
    }

    cout<<dp_1[w]<<endl;
    return 0;
}

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