多重揹包:有n種物品,每個物品的重量爲w[i],每個物品的價值爲h[i],每種物品有c[i]個。
最樸素的做法中,我們把c[i]個物品i看成c[i]個不同的物品,進而轉化成了0-1揹包。然後在0-1揹包的基礎上我們還可以進行二進制優化
二進制優化
我們知道:
20,21,22,23,,,2n可以組成1~2(n+1)-1中的任意數(每個數只能用一次)
所以我們可以把c[i]個相同的物品,看成這樣的幾堆物品:
10=1+2+4+3
15=1+2+4+8
36=1+2+4+8+16+5
後面的4、4、6(Olog(n))堆物品就可以代替前面的10、15、36(O(n))堆,因爲他們可以組成1~c[i]的任意數,複雜度也就在這裏降低了
for(int i=1; i<=n; i++)
{
for(int k=1; k<=c[i]; k<<=1)
{
for(int j=v; j>=k*w[i]; j--)
dp[j]=max(dp[j],dp[j-k*w[i]]+k*h[i]);
c[i]-=k;
}
if(c[i])
for(int j=v; j>=c[i]*w[i]; j--)
dp[j]=max(dp[j],dp[j-c[i]*w[i]]+c[i]*h[i]);
}
轉化爲有限制的完全揹包
0-1揹包中我們爲了保證每件物品只參與了一次,在第二層循環中我們是從後往前遍歷for(j=v; j>=w[i]; j–)。但在完全揹包中,每件物品都可以用無數次,所以直接從前往後遍歷一遍就可以for(j=w[i]; j<=v; j++)。他們的複雜度都是O(n*v)(兩層循環)
多重揹包中,每件物品的個數是有限的,在轉化成0-1揹包以及它的二進制優化中,我們另外加了一層循環來限制每件物品的數量。那麼我們如果想將多重揹包轉化爲完全揹包,又怎麼保證結果中每件物品使用的數量不會超過c[i]呢?————我們可以用一個數組cou[]來記錄當前物品使用的次數。當我們發現當前物品的使用數量超過c[i]時退出循環就可以了。
例題:http://acm.hdu.edu.cn/showproblem.php?pid=1059
題意:瑪莎和比爾有一堆彈珠,他們想把彈珠分開,一人一份。如果彈珠的大小一樣就好了,那樣直接一人一半。不幸的是,這些彈珠大小不一。所以他們倆就給每個彈珠編了一個號,從1到6,現在他們想把這些彈珠分成兩份,只要他們最後得到的總值一樣就可以。然而問題又來了,因爲他們發現如果彈珠是1、3、4、4這種情況,他們任然無法平分。所以現在他們想讓你確定一下他們這堆彈珠是否能夠被平分。
代碼:
#include<stdio.h>
#include<string.h>
#include<algorithm>
typedef long long ll;
const int maxn=1e4+100;
int a1,b,c,d,e,f,k=1,dp[101000],cou[100010];
int main()
{
while(scanf("%d%d%d%d%d%d",&a1,&b,&c,&d,&e,&f),a1||b||c||d||e||f)
{
int sum=a1+b*2+c*3+d*4+e*5+f*6;
int a[10];
a[1]=a1,a[2]=b,a[3]=c,a[4]=d,a[5]=e,a[6]=f;
if(sum%2)
{
printf("Collection #%d:\nCan't be divided.\n\n",k++);
continue;
}
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int i=1; i<=6; i++)
{
//memset(cou,0,sizeof(cou));
for(int q=0;q<=sum/2;q++)
cou[q]=0;//每次都要初始化爲0
for(int j=i; j<=sum/2; j++)
{
if(cou[j-i]>=a[i])
break;
if(!dp[j]&&dp[j-i])
{
dp[j]=1;
cou[j]=cou[j-i]+1;
}
if(dp[sum/2])
break;
}
}
if(dp[sum/2])
printf("Collection #%d:\nCan be divided.\n\n",k++);
else
printf("Collection #%d:\nCan't be divided.\n\n",k++);
}
return 0;
}
其他例題:Coins