經典揹包問題----(01揹包、完全揹包、多重揹包)

最近在學DP,上週六ACM集訓隊花一整天的時間共同探討了最經典的DP--揹包問題,對這類問題研究也挺深入的,感謝各隊友及老師的講解,覺得受益匪淺!

(1)01揹包

01揹包算是最基礎的揹包問題了,意思就是共有N個物品,一個揹包。各物品的重量爲Wi,價值爲Vi,揹包能承受的最大重量爲W,求揹包能裝進去物品的最大價值!分析:其一是變量元素,可知與該結果有關係的就是重量和價值,其次是狀態轉移。那麼顯然子問題就是當前的揹包重量能裝的最大價值,用數組F[i][j]表示的話,揹包的剩餘重量爲j,i爲當前物品,F[i][j]保存的就是揹包在j容量下裝的最大價值,即把1,2,3...i-1個物品裝到容量爲j的揹包中的最大價值。對於第i個物品,有兩種選擇,放或者不放,當j<wi時,肯定是不能放的,那麼F[i][j]=F[i-1][j],若j>=Wi,放第i件物品時,剩餘揹包容量爲j-Wi,此時價值爲F[i][j]=F[i-1][j-Wi]+Vi,做價值最大的選擇,則:F[i][j]=MAX(F[i-1][j],F[i-Wi]+Vi。可由底層到頂層推,那麼F[1][W]爲最終值,代碼如下:

int DP1()
{
    for(int j=W;j<=0;j++)
    {
        if(j<w[N])
          f[N][j]=0;
        else
          f[N][j]=v[i];
    }
    for(int i=N-1;i>=1;i--)
        for(int j=0;j<=W;j++)
       {
        if(j<w[i])
            f[i][j]=f[i+1][j];
        else
            f[i][j]=max(f[i+1][j],f[i+1][j-w[i]]+v[i]);
       }
    return f[1][W];
}

優化:由於F[i][j]是從前一個狀態F[i-1][j]得到的,只與前一個狀態有關,那麼即可優化爲一維滾動數組F[],需注意F必須是從右往左計算,因爲F[j]保存的是f[i-1][j]的值,F[j-Wi]保存的是f[i-1][j-Wi]的值,而不是f[i][j-Wi],這樣,F[j]保存的就是max(f[i-1][j],f[i-1][j-Wi])覆蓋原來的f[i-1][j]//注意:便於敘述,這裏F[]表示優化的滾動數組,f[][]是原來的二維數組。代碼如下:

int DP1()
{
  memset(f,0,sizeof(f));
  for(int i=1;i<=N;i++)
    for(int j=W;j>=w[i];j--)
      f[j]=max(f[j],f[j-w[i]+v[i])
    return f[W];
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(2)完全揹包

完全揹包是在01揹包的基礎上,物品爲的數量由一個變爲無限個,求滿足重量的條件下最大價值。由上述的01揹包,很容易就能想到通過枚舉每種物品的數量來求最大值,那麼需要再加一層循環來限定第i件物品的數量,此時公式爲:F[i][j]=MAX(F[i-1][j],F[i-1][j-k*w[i]]+k*v[i])1<=k<=W/Wi不過很遺憾,這種算法的複雜度非常高,再仔細考慮一下,會發現若兩件物品i、j滿足v[i]>=v[j]且w[i]<=w[j],則將物品j去掉,不用考慮。即,如果一個物品A是佔的地少且價值高,而物品B是佔地多,但是價值不怎麼高,那麼肯定是優先考慮A物品的。那麼公式即爲:F[i][j]=MAX(F[i-1][j],F[i][j-w[i]]+v[i],代碼如下:


for(int i=1;i<=N;i++)
    for(int j=0;j<=W;j++)
    {
      if(j<w[i])
        f[i][j]=f[i-1][j];
      else
        f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i];
    }

-------------------------------------------------------------------------------------------------------

(3)多重揹包

多重揹包是每件物品都有給定的數量,如果按照安全揹包的思想,每件物品的數量爲num[i],即1<=k<=num[i],若按三層循環來寫,複雜度也挺高!那麼有一個非常奇妙的二進制思想,任何數都可以用二進制來表示,如果一個數N能用1,2,4,8,16....的和來表示的話,那麼從1到N之間的任意數字都可以用這些數來表示,比如第i種物品的數量爲7,重量爲2,價值爲3,分析:7可分爲1,2,4即該種物品被分成重量爲2*1,2*2,2*4價值爲3*1,3*2,3*4的三種物品,那麼就大大減少了物品的數量,推測以一億個物品按這種方式最多分爲30多個物品,這樣就能用01揹包的知識來解決這個問題,代碼如下:

struct ss

{

   int x;//物品重量

   int y;//物品價值

   int z;//物品數量

}s[1000];//假設1000種物品

int DP1()
{
   int t=-1;
   for(int i=1;i<=N;i++)
   {
    t++;
    int sum=1;
    int sum1=1;

    w[t]=s[i].x;

    v[t]=s[i].y;
    while(sum1<s[i].z)
    {
       sum*=2;
       sum1+=sum;
       if(sum1<s[i].z)
       {
        w[t]=sum*s[i].x;
        v[t]=sum*s[i].y;
       }
    }
       if(sum1!=s[i].z)
       {
           t++;
           w[t]=(s[i].z-(sum1-sum))*s[i].x;
           v[t]=(s[i].z-(sum1-sum))*s[i].y;
       }
   }
    memset(f,0,sizeof(f));
    for(int i=0;i<=t;i++)
       for(int j=W;j>=w[i],j--)
          f[j]=max(f[j],f[j-w[i]+v[i]);
    return f[W];
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章