揹包問題(一)

詳細的參見揹包九講,地址:http://download.csdn.net/detail/u014007510/7258089

i表示前i件物品,j表示空間大小

01揹包:每件物品可放一次,狀態轉移方程:

二維的:       f[i][j]=max(f[i-1][j],f[i-1][j-cost[i]]+worth[i]),放與不放中取最大的

先寫成i行j列的矩陣;
觀察到:f[i][j]的取值只與f[i-1]層的數據有關,每次更新第j列的數據就行了,那麼可以將二維的動態轉移方程寫成一維的形式;
f[j]=max(f[j],f[j-cost[i]]+worth[i])   ①
ps:    其實就是把f裏面的二維i都去掉,對比上面的方程會發現神奇之處,暫且留個小問題下面講
01揹包是核心思想必須要完全理解它。

完全揹包:
二維的:   f[i][j]=max(f[i-1][j],f[i-k][j-k*cost[i]]+k*worth[i])   或者  f[i][j]=max(f[i-k][j-k*cost[i]]+k*worth[i]) 
ps: 轉換成01揹包來做了,k的可能取值:0<=V-k*cost[i]<=V,枚舉k就可以了。
上面的動態轉移方程看起來比較複雜,而且也不太美,有沒有更簡潔的方程呢?
簡單的二維方程: f[i][j]=max[f[i-1][j],f[i][j-cost[i]]+worth[i]]  
這個方程比較有意思,f[i-1][j]表示在j的空間中放前i-1件的物品可達到的最大價值,f[i][j-cost[i]]+worth[i]表示在j-cost[i]的空間裏放前i件物品可達到的最大價值的基礎上 放一個第i件物品,f[i][j]的取值必然是由這兩種情況得來的!其實這個方程就是:在可能放多個與不放中取最大的。
可能放多個的表示方法:就是在可能已放(即f[i][j-cost[i]])的基礎上再放一個worth[i]。(講的清楚吧^_^)
一維的: f[j]=max(f[j],f[j-cost[i]]+worth[i])       ②同樣是在放與不放中取最大的


呵呵,看到這兒請比較①②的,看起來是完全一樣的,那麼應該怎麼區分他們呢?
注意:①中max裏的f[j]表示的是放前i-1件的最大值,②中max裏的f[j]表示的是放前i件的最大值;
實現:
①:
void ZO(int V,int C,int W)//V表示整個空間的大小,C指第i種物品的大小,W表示第i種物品的價值
{
    for(int v=V;v>=C;v--)//倒序更新,保證F[v]是前i-1件的最大值
    {
        F[v]=max(F[v],F[v-C]+W);
    }
}


②對每個i執行
void CO(int V,int C,int W)
{
    for(int v=C;v<=V;v++)//順序更新,保證F[v]是前i件的最大值
        F[v]=max(F[v],F[v-C]+W);
}


多重揹包:
二維的:   f[i][j]=max(f[i-1][j],f[i-k][j-k*cost[i]]+k*worth[i])   或者  f[i][j]=max(f[i-k][j-k*cost[i]]+k*worth[i]) 
ps: 和完全揹包類似,0<=V-k*cost[i]<=V且0<=k<=num[i]
有了前面的基礎可以想到:把第i件做多取num[i]件理解爲有num[i]件價值爲worth[i]的物品,再用01揹包做就行了。
但是有沒有更高效的方法呢?
先看這個問題:一個數字n可以寫成n=2^0+2^1+2^3+...+2^i+k,1<=2^i<=n,0<=k<=n-2^i
以19爲例:寫成10011>1111,進而19=10011=1+10+100+1000+(10011-1-10-100-1000)=1+2+4+8+4
通過上面的簡單非嚴格說明可以知道,n=2^0+2^1+2^3+...+2^i+k,1<=2^i<=n,0<=k<=n-2^i
回到原問題,
將num[i]件物品分解爲2^0件的組合體+2^1件的組合體+2^3件的組合體+...+2^件的組合體i+k件的組合體。這種方法比前一種好多了。
實現:
對每個i
void MU(int V,int C,int W,int M)
{
    if(C*M>=V)//相當於完全揹包
        CO(V,C,W);
    else
    {
        int k=1;
        while(k<M)
        {
            ZO(V,k*C,k*W);//分解爲01揹包
            M=M-k;
            k=2*k;
        }
        ZO(V,M*C,M*W);
    }
}
混合揹包問題:
其實就是多重揹包問題,不過num[i]的取值有區別罷了:屬於01揹包的num[i]=1,屬於完全揹包的num[i]=(V/cost[i]+1)。
當然也可以這麼做

for i=1..N

    if 第i件物品屬於01揹包

        ZO(V,cost[i],worth[i])

    elseif 第i件物品屬於完全揹包

        CO(V,cost[i],worth[i])

    else if 第i件物品屬於多重揹包

        MU(V,cost[i],worth[i],num[i])



最後初始化問題:
①恰好裝滿:f[0]=0,f[1,2,3.....n]=-∞,理解:對0,1,2...n大的空間都什麼也不裝,只有0可以裝,其他都是非法的
②不用恰好裝滿:f[0,1,2...n]=0,理解,對0,1,2...n大的空間都什麼也不裝,都是合法的,都可爲0
發佈了47 篇原創文章 · 獲贊 6 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章