多重揹包的 二進制優化 / 轉化爲有限制的完全揹包

多重揹包:有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

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