多重揹包二進位制分解思想講解

Dream__Boy發表於2014-04-14

轉載自http://blog.csdn.net/jkay_wong/article/details/7240588

在揹包九講裡面將多重揹包轉化為01揹包,並且進行時間優化,有利用到一個二進位制分解的思想。

下面是在網上搜尋之後得到的一個關於二進位制分解思想的講解和實現

多重揹包二進位制分解思想講解

  1. /**  
  2.     在這之前,我空間好像轉過一個揹包九講,現在我就只對  
  3.     01揹包和多重揹包有點印象了  
  4.   
  5.     先說下 01 揹包,有n 種不同的物品,每個物品有兩個屬性  
  6.     size 體積,value 價值,現在給一個容量為 w 的揹包,問  
  7.     最多可帶走多少價值的物品。  
  8.   
  9.     int f[w+1];   //f[x] 表示揹包容量為x 時的最大價值  
  10.     for (int i=0; i<n; i++)  
  11.         for (int j=w; j>=size[i]; j++)  
  12.             f[j] = max(f[j], f[j-size[i]]+value[i]);  
  13.   
  14.     如果物品不計件數,就是每個物品不只一件的話,稍微改下即可  
  15.     for (int i=0; i<n; i++)  
  16.         for (int j=size[i]; j<=w; j++)  
  17.             f[j] = max(f[j], f[j-size[i]]+value[i]);  
  18.   
  19.     f[w] 即為所求  
  20.   
  21.     初始化分兩種情況  
  22.     1、如果揹包要求正好裝滿則初始化 f[0] = 0, f[1~w] = -INF;  
  23.     2、如果不需要正好裝滿 f[0~v] = 0;  
  24.   
  25.     多重揹包問題要求很簡單,就是每件物品給出確定的件數,求  
  26.     可得到的最大價值  
  27.   
  28.     多重揹包轉換成 01 揹包問題就是多了個初始化,把它的件數C 用  
  29.     分解成若干個件數的集合,這裡面數字可以組合成任意小於等於C  
  30.     的件數,而且不會重複,之所以叫二進位制分解,是因為這樣分解可  
  31.     以用數字的二進位制形式來解釋  
  32.     比如:7的二進位制 7 = 111 它可以分解成 001 010 100 這三個數可以  
  33.     組合成任意小於等於7 的數,而且每種組合都會得到不同的數  
  34.     15 = 1111 可分解成 0001  0010  0100  1000 四個數字  
  35.     如果13 = 1101 則分解為 0001 0010 0100 0110 前三個數字可以組合成  
  36.     7以內任意一個數,加上 0110 = 6 可以組合成任意一個大於6 小於13  
  37.     的數,雖然有重複但總是能把 13 以內所有的數都考慮到了,基於這種  
  38.     思想去把多件物品轉換為,多種一件物品,就可用01 揹包求解了。  
  39.   
  40.   
  41.     看程式碼:  
  42.     int n;  //輸入有多少種物品  
  43.     int c;  //每種物品有多少件  
  44.     int v;  //每種物品的價值  
  45.     int s;  //每種物品的尺寸  
  46.     int count = 0; //分解後可得到多少種物品  
  47.     int value[MAX]; //用來儲存分解後的物品價值  
  48.     int size[MAX];  //用來儲存分解後物品體積  
  49.   
  50.     scanf("%d", &n);    //先輸入有多少種物品,接下來對每種物品進行分解  
  51.   
  52.     while (n--) {   //接下來輸入n中這個物品  
  53.         scanf("%d%d%d", &c, &s, &v);  //輸入每種物品的數目和價值  
  54.         for (int k=1; k<=c; k<<=1) { //<<右移 相當於乘二  
  55.             value[count] = k*v;  
  56.             size[count++] = k*s;  
  57.             c -= k;  
  58.         }  
  59.         if (c > 0) {  
  60.             value[count] = c*v;  
  61.             size[count++] = c*s;  
  62.         }  
  63.     }  
  64.   
  65.     現在用count 代替 n 就和01 揹包問題完全一樣了  

下面是利用上面的講解,對HDOJ 2191進行解答,程式碼如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int main()  
  5. {  
  6.     int nCase,Limit,nKind,i,j,k,  
  7.         v[111],w[111],c[111],dp[111];  
  8.     //v[]存價值,w[]存尺寸,c[]存件數  
  9.     //在本題中,價值是米的重量,尺寸是米的價格  
  10.   
  11.     int count,Value[1111],size[1111];  
  12.     //count儲存分解完後的物品總數  
  13.     //Value儲存分解完後每件物品的價值  
  14.     //size儲存分解完後每件物品的尺寸  
  15.   
  16.     cin>>nCase;  
  17.     while(nCase--)  
  18.     {     
  19.         count=0;  
  20.         cin>>Limit>>nKind;  
  21.         for(i=0;i<nKind;i++)  
  22.         {  
  23.             cin>>w[i]>>v[i]>>c[i];  
  24.               
  25.             //對該種類的c[i]件物品進行二進位制分解  
  26.             for(j=1;j<=c[i];j<<=1)  
  27.             {  
  28.                 //<<右移1位,相當於乘2  
  29.                 Value[count]=j*v[i];  
  30.                 size[count++]=j*w[i];  
  31.                 c[i] -= j;  
  32.             }  
  33.             if(c[i] > 0)  
  34.             {  
  35.                 Value[count]=c[i]*v[i];  
  36.                 size[count++]=c[i]*w[i];  
  37.             }  
  38.         }  
  39.   
  40.         //經過上面對每一種物品的分解,  
  41.         //現在Value[]存的就是分解後的物品價值  
  42.         //size[]存的就是分解後的物品尺寸  
  43.         //count就相當於原來的n  
  44.   
  45.         //下面就直接用01揹包演算法來解  
  46.         memset(dp,0,sizeof(dp));  
  47.   
  48.         for(i=0;i<count;i++)  
  49.             for(j=Limit;j>=size[i];j--)  
  50.                 if(dp[j] < dp[j-size[i]] + Value[i])  
  51.                     dp[j]=dp[j-size[i]]+Value[i];  
  52.           
  53.         cout<<dp[Limit]<<endl;  
  54.     }  
  55.     return 0;  
  56. }  

在揹包九講裡面,他的實現方法和這個是不一樣的,他是利用01揹包和完全揹包來配合實現的,下面是那個版本的實現

  1. /* 
  2. HDOJ 2191 
  3. 多重揹包用二進位制轉化的思想,進行優化 
  4. */  
  5.   
  6. #include <iostream>  
  7. using namespace std;  
  8.   
  9. int weight[110],Value[110],num[110];  
  10. int f[1100];  
  11. int limit;  
  12.   
  13. inline void ZeroOnePack(int w,int v)  
  14. {  
  15.     int j;  
  16.     for(j=limit;j>=w;j--)  
  17.     {  
  18.         if(f[j-w]+v > f[j])  
  19.             f[j]=f[j-w]+v;  
  20.     }  
  21. }  
  22.   
  23. inline void CompletePack(int w,int v)  
  24. {  
  25.     int j;  
  26.     for(j=w;j<=limit;j++)  
  27.     {  
  28.         if(f[j-w]+v > f[j])  
  29.             f[j]=f[j-w]+v;  
  30.     }  
  31. }  
  32.   
  33. inline void MultiplePack(int w,int v,int amount)  
  34. {  
  35.     if(amount * w >= limit)  
  36.     {  
  37.         CompletePack(w,v);  
  38.         return ;  
  39.     }  
  40.     for(int k=1;k<amount;k<<=1)  
  41.     {  
  42.         ZeroOnePack(k*w,k*v);  
  43.         amount -= k;  
  44.     }  
  45.     ZeroOnePack(amount*w,amount*v);  
  46. }  
  47.   
  48. int main()  
  49. {  
  50.     int T,n;  
  51.     cin>>T;  
  52.     while(T--)  
  53.     {  
  54.         cin>>limit>>n;  
  55.           
  56.         for(int i=0;i<n;i++)  
  57.             cin>>weight[i]>>Value[i]>>num[i];  
  58.           
  59.         memset(f,0,sizeof(f));  
  60.           
  61.         for(i=0;i<n;i++)  
  62.             MultiplePack(weight[i],Value[i],num[i]);  
  63.           
  64.         cout<<f[limit]<<endl;  
  65.     }  
  66.     return 0;  
  67. }

相關文章