dp之揹包總結篇

前言:揹包問題在dp中可以說是經典,作爲一個acmer,到現在才正式學習dp,可以說是比較失敗的。我個人比較認同一點,想要做一個比較成功的acmer,dp、搜索、數學必須精練,比較遺憾的是,對我我自身而言,並沒有早早的認識到這個問題,不過現在知道了,還有一年,也不算晚。還有,我建議學揹包的童鞋,都看揹包九講……
dp之01揹包
01揹包,做爲揹包中最基礎的一類揹包,必須要掌握好,當然我這裏說的掌握好,並不是說,你橫掃hdu或者poj等oj上01揹包模板題就可以的,記得很久以前,剛開始做揹包問題,一天在hdu水了七八道,就自以爲揹包就是個模板,唉,真心不知道當時的自己是有多膚淺……….
01揹包的動態轉移方程,揹包九講上有詳細的講解,這裏不再複述,就想說下,一維的和二維的轉移方程都需要深刻理解,不要覺得任何時候01揹包都可以轉化爲1維的數組
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+val[i]);
dp[j]=max(dp[j],dp[j-v[i]]+val[i]);
此外,還想說下,01揹包爲什麼體積要從v—0?這個問題,揹包九講上是說可以每件物品只拿一次,恩,我覺得可以自己推推,這樣理解會更加深刻…….
1、UVA624(記錄路徑問題)
總得來說,不管是01揹包還是完全揹包,其動態轉移每次只有兩種狀態在轉移,就說這道題目,
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+val[i]) 對於dp[i][j]來說,它只能使由兩個狀態中的一個轉移過來的,要麼取一件,要麼不取,那麼我們再開一個二維數組s[i][j],0表示不取,1表示取,那麼不取是dp[i-1][j],取是dp[i-1][j-v[i]]+val[i];這樣,數組s[i][j]就記錄下了每次動態轉移的方向,在遞歸調用,打印路徑,結果就出來了,具體可以看http://www.cnblogs.com/ziyi–caolu/archive/2013/04/10/3012578.html
2、UVA562(平分錢幣問題)
題目大意:給定n個硬幣,要求將這些硬幣平分以使兩個人獲得的錢儘量多,求兩個人分到的錢最小差值。
這類問題的關鍵就在平衡,而就這到題目而言,01揹包本身求的就是在體積小於等於sum的情況下的最大值,那麼這些硬幣本身既當體積又當價值,把總價值求出來/2,再套用模板……可以說比較水的平衡問題,具體看:http://www.cnblogs.com/ziyi–caolu/archive/2013/04/10/3012755.html
3、組成n塊錢,有多少種的問題
這類題目,一般是這樣描述的:給你n塊錢,有m種錢幣,每種錢幣只能用一次,問組成n塊錢有多少種方法……..
對於這類題目,我更多的感受是覺得是到遞推題,其實,有好多的dp都是一個遞推,然後記錄更新狀態的過程,要組成dp[j],那麼假設有一個5快的錢幣,那麼首先要判斷dp[j-5]有沒有組成,這裏我比較喜歡用0表示沒有這個狀態,>0表示這個狀態存在,那麼在初始化dp數組的時候,dp[0]=1;然後套用揹包模板,其動態轉移方程(倒不如說遞推式)爲dp[j]+=dp[j-5];
上面是常規題目,下面爲01揹包的變種題:
4、poj2184(兩個屬性最大和,平移問題)
題意:給定兩個屬性,求這兩個屬性的和的最大值………
思路:對於這種題目,我們可以嘗試將一個屬性當作容量,另一個屬性當作價值,然後考慮dp…….這個題目屬性還可以是負數,那麼可以採取平移1000個單位,使其不存在負數問題。將第一個屬性往後平移1000個單位,然後推導其動態轉移方程,若是dp[i],代表當加入第一個屬性加到i時,符合題意的第二個屬性的最大值……題意是兩個屬性的和的最大值,那麼動態轉移方程必然不是dp[j]=max(dp[j],dp[j-s[i][0]]+s[i][1]),因爲這個動態轉移方程固然可以求出第二個屬性的最大值,但別忘了題意要求第一個屬性與第二個屬性的和的最大值,那麼,第一個屬性平移了1000個單位,在考慮動態轉移時,是必須要將這個考慮進去的。可以開一個a數組記錄路徑,然後根據題意,
動態轉移方程應該爲dp[j]=max(dp[j]-a[j]*1000,dp[j-s[i][0]]+s[i][1]-(a[j-s[i][0]]+1)*1000),一開始a數組全部賦值爲0,所以需要a[j-s[i][0]]+1…..因爲它新加入了一個值。考慮這個方程,dp數組的初始全部賦值爲負無窮大,dp[0]=0。
注意一點,在歷遍查找最大值的時候,dp[i]>0,i-a[i]*1000>0

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std; 
#define maxx 1000005
int s[maxx][2],dp[maxx],a[maxx];
int main()
{
    int n;
    while(scanf("%d",&n)>0)
    {
        int sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d",&s[i][0],&s[i][1]);
            if(s[i][0]<0&&s[i][1]<0)
            {
                i--;
                n--;
                continue;
            }
            s[i][0]+=1000;
            sum+=s[i][0];
        }
        memset(a,0,sizeof(a));
        for(int i=0;i<=sum;i++)
        dp[i]=-maxx;
        dp[0]=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=sum;j>=s[i][0];j--)
            if(dp[j]-a[j]*1000<dp[j-s[i][0]]+s[i][1]-(a[j-s[i][0]]+1)*1000)
            {
                dp[j]=dp[j-s[i][0]]+s[i][1];
                a[j]=a[j-s[i][0]]+1;
            }
        }
        int maxn=0;
        for(int i=1;i<=sum;i++)
        if(maxn<dp[i]-a[i]*1000+i&&dp[i]>=0&&i-a[i]*1000>=0)
        maxn=dp[i]-a[i]*1000+i;
        printf("%d\n",maxn);
    }
    return 0;
}

5、hdu2639(第k優解問題)
題意:給出一行價值,一行體積,讓你在v體積的範圍內找出第k大的值…….(注意,不要 和它的第一題混起來,它第一行是價值,再是體積)
思路:首先dp[i][j]代表的是在體積爲i的時候第j優解爲dp[i][j]……那麼,我們就可以這樣思考,i對應體積,那麼如果只是一維的dp[i],代表的應該是體積爲i時的最大值,那麼同理,dp[i][1]代表的是體積爲i時的最大值,那麼我們就可以退出兩種動態,dp[i][m],dp[i-s[i][0]][m]+s[i][1]…..然後把這兩種狀態開個兩個數組分別保存起來,再合併出體積爲i時的前k優解……依次後推,直到dp[v][k]…….


#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
#define max(x,y) x>y? x:y
typedef int ss;
ss dp[1005][100],s[1005][2];
ss a[50],b[50];

int main()
{
    int text;
    scanf("%d",&text);
    while(text--)
    {
        ss n,v,k;
        scanf("%d%d%d",&n,&v,&k);
        for(ss i=1;i<=n;i++)
        scanf("%d",&s[i][1]);
        for(ss i=1;i<=n;i++)
        scanf("%d",&s[i][0]);
        memset(dp,0,sizeof(dp));
        if(k==0)
        {
            printf("0\n");
            continue;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=v;j>=s[i][0];j--)
            {
                for(int m=1;m<=k;m++)
                {
                    a[m]=dp[j][m];
                    b[m]=dp[j-s[i][0]][m]+s[i][1];
                }
                int x=1,y=1,w=1;
                a[k+1]=b[k+1]=-1;  //這個地方要注意,因爲有可能a或者b數組先比較完 

                while(w<=k&&(x<=k||y<=k))
                {
                    if(a[x]>b[y])
                    {
                        dp[j][w]=a[x++];
                    }
                    else
                    {
                        dp[j][w]=b[y++];
                    }
                    if(w==1||dp[j][w]!=dp[j][w-1])       //這是去重..... 
                    w++;
                    //printf("111\n");
                }
            }

        }
        //for(int i=1;i<=k;i++)
    printf("%d\n",dp[v][k]);
    }
    return 0;
} 

6、hdu3644(帶限制的01揹包)
題意:買東西,每個東西有三個特徵值,p代表價格,q代表你手中錢必須不低於q才能買這個物品,v代表得到的價值。
mark:又是變種01揹包,每做一個變種的,就是一種提高。。
按照q - p以由大到小的順序排序,然後進行01揹包的DP即可。

#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAXN=5005;
int dp[MAXN];
struct Node
{
    int p,q,v;
}node[505];
bool cmp(Node a,Node b)
{
    return  (a.q-a.p)<(b.q-b.p);
}    
int main()
{
    int n,m;
    int i,j;
    int p,q,v;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(i=0;i<=m;i++)
          dp[i]=0;
        for(i=0;i<n;i++)
        {
            scanf("%d%d%d",&node[i].p,&node[i].q,&node[i].v);
        }    
        sort(node,node+n,cmp);
        for(i=0;i<n;i++)
        {
            for(j=m;j>=node[i].p;j--)
            {
                if(j>=node[i].q)
                  dp[j]=max(dp[j],dp[j-node[i].p]+node[i].v);
            }    
        }
        int ans=0;
        for(i=1;i<=m;i++)
          if(ans<dp[i])  ans=dp[i];
        printf("%d\n",ans);

    }    
    return 0;
}

dp之完全揹包
完全揹包較之01揹包,唯一區別就是01揹包的物品每個只能取一次,而完全揹包可以取無限次
dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+val[i])
dp[j]=max(dp[j],dp[j-v[i]]+val[i]);
1、poj3181(高精度揹包)
這道題目要用到大數加法,其他的沒有什麼難度,當然,你的大數加法如果寫的比較挫,是會超時的……解題報告:http://www.cnblogs.com/ziyi–caolu/p/3211091.html
2、poj1787(多重揹包轉化爲完全揹包,以及路徑記錄 推薦)
題意:有四種硬幣,1分,5分,10分,25分,分別有a,b,c,d種,給出一個n分錢,要求你求出組成n分錢最多需要的硬幣數量,並且輸出組成它的各種硬幣的數量……
學到的東西:這個題目我是用兩種方法做的,一個是完全揹包,一個是多重揹包。做完這個題目,我對揹包的理解可以說上了個層次……還有記錄路徑的方法,反過來求出各個硬幣的數量,都是我以前做的題目沒有涉及到的…….
要求出各個硬幣有多少種,只需要記錄路徑,在開一個數組統計p-path[p],爲什麼可以如此?很容易想到path[p]=p-v[i]
如此,p-path[p]==p-(p-v[i])==v[i],而v[i]正好是硬幣的價值……..這道題目令我思考到一個東西,一般的揹包問題的初始值都是將dp全部賦值爲0的,而在這邊dp[0]==1,並且在動態轉移的過程中,還限制了dp[j]>0纔可以轉移,與揹包的模板的動態轉移不同啊?爲什麼要這樣呢?
在一般的dp題目裏面,有體積,價值,所要求的不是最大價值就是最小价值,而定義的dp[i][j]意義是裝有i件物品,體積爲j的最大值爲dp[i][j],簡化爲一維的dp[j]代表的是在體積爲j的時候最大價值爲dp[j]。仔細觀察,可以發現,在dp[j-v[i]]時,dp[j]本身就可以從dp[j-v[i]]那邊得到值,因爲dp[j-v[i]]這個地方,它可以組成dp[j]……也就是說,它不需要去判斷在dp[j-v[i]]前面是否有數可以組成dp[j-v[i]],因此dp[j-v[i]]爲不爲0,不影響最終結果……
而這個題目卻不同,需要賦值dp[0]=1;在動態轉移的時候還得保證dp[j-v[i]]>0,這是因爲題意是要求組成n分錢需要的最多硬幣數量,那麼你要可以組成dp[n],dp[n-v[i]]必須要可以組成,否則就會出錯…….同理,這道題目原理如此,在做給出n分錢,每種錢幣有a,b,c,…..種,求組成n分錢最多的種數,也是要賦值dp[0]=1的,原理是一樣的……..
這告誡我,在做dp題目時,要仔細思考好其前後的關係,以及中間推導至最後的關係…..

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[10010],ans[10010],num[10010],path[10010];
int p,c[5]={0,1,5,10,25},t[5];
int main()
{
    while(scanf("%d %d %d %d %d",&p,&t[1],&t[2],&t[3],&t[4])>0&&(p+t[1]+t[2]+t[3]+t[4]))
    {
        memset(dp,0,sizeof(dp));
        memset(ans,0,sizeof(ans));
        memset(path,0,sizeof(path));
        dp[0]=1;
        for(int i=1;i<=4;i++)
        {
            memset(num,0,sizeof(num));
            for(int j=c[i];j<=p;j++)
            if(dp[j-c[i]]&&dp[j-c[i]]+1>dp[j]&&num[j-c[i]]<t[i])   //一般來說,完全揹包的硬幣是沒有限制的,後一個數必然可以由前面的某個數組成,所以也就不需要dp[j-c[i]]>0,<br>但是,這次用到的完全揹包其硬幣數受到了限制,也就導致有些數根本不可能組成,所以要把這些數排除 
            {
                dp[j]=dp[j-c[i]]+1;
                num[j]=num[j-c[i]]+1;
                path[j]=j-c[i];
            }
        }
        int i=p;
        if(dp[p]>0)
        {
            while(i!=0)
            {
                ans[i-path[i]]++;
                i=path[i];
            }
            printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n",ans[1],ans[5],ans[10],ans[25]);
        }
        else  printf("Charlie cannot buy coffee.\n");
    }
    return 0;
}
二進制轉化爲01揹包:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
struct node
{
    int num;
    int cout;
    int dw;
}w[20001];
int dp[20001],c[5]={0,1,5,10,25},t[5],path[20001][2],ans[20001];
int p;
int main()
{
    while(scanf("%d%d%d%d%d",&p,&t[1],&t[2],&t[3],&t[4])>0&&(p+t[1]+t[2]+t[3]+t[4]))
    {
        int cnt=0;
        for(int i=1;i<=4;i++)
        {
            int k=1;
            while(t[i]-k>0)
            {
                w[cnt].num=k*c[i];
                w[cnt].cout=k;
                w[cnt++].dw=c[i];
                t[i]-=k;
                //if(i==1)
                //printf("%d\n",w[cnt-1].dw);
                k*=2;
            }

            w[cnt].num=t[i]*c[i];
            w[cnt].cout=t[i];
            w[cnt++].dw=c[i];
            //if(i==1)
            //    printf("%d\n",w[cnt-1].dw);
        }
        memset(dp,0,sizeof(dp));
        memset(path,0,sizeof(path));
        memset(ans,0,sizeof(ans));
        dp[0]=1;
        for(int i=0;i<cnt;i++)
        {
            for(int j=p;j>=w[i].num;j--)
            if(dp[j-w[i].num]>0&&dp[j]<dp[j-w[i].num]+w[i].cout)  //多重揹包的二進制拆分其原理就是轉化成01揹包來處理,而01揹包是從n推導至0,在i==1時,dp[n-w[i].num]+w[i].cout>dp[n]除非w[i].cout==0,否則就是必然的,但是同時,你要可以組成dp[n],那麼你首先要可以組成dp[n-w[i].num],那麼也就是說dp[n-w[i].num]必須要>0,纔可以進行動態轉移 
            {
                dp[j]=dp[j-w[i].num]+w[i].cout;
                //printf("%d %d %d %d\n",w[i].num,w[i].dw,w[i].cout,dp[j]);
                path[j][0]=j-w[i].num;
                path[j][1]=i;
            }
        }
        //printf("%d\n",dp[p]);
        if(dp[p]!=0)
        {
            while(p!=0)
            {
                int tmp=p-path[p][0];
                int j=path[p][1];
                ans[w[j].dw]+=w[j].cout;
                p=path[p][0];
            }
             printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n",ans[1],ans[5],ans[10],ans[25]);
        }
        else printf("Charlie cannot buy coffee.\n");
    }
    return 0;
}

3、poj2063題意:求投資k年獲得最大投資,每年都選最大利息的方案進行投資k年後就可以得到最多的人民幣。
注意:每一年收到的利息都可以作爲下一年的本金……其實從測試數據來看,是很好看出來的……
思路:將每一年的“體積”加上利息就好,當然,數據太大,可以除以100減少時間和空間複雜度……
解題報告:http://www.cnblogs.com/ziyi–caolu/p/3214100.html

dp之多重揹包

1、hdu1114(水題)
2、hdu1059
題意:價值爲1,2,3,4,5,6. 分別有n[1],n[2],n[3],n[4],n[5],n[6]個。求能否找到滿足價值剛
好是所有的一半的方案。 思路:簡單的多重揹包,我建議多重揹包都用二進制拆分優化下…….. 解
題報告:http://www.cnblogs.com/ziyi–caolu/p/3216818.html

3、poj1217
題意:有現今cash,和n種錢幣,每種錢幣有ni個,價值爲di,求各種錢幣組成的不超過cash的最大錢數
…….
思路:二進制拆分轉化爲01揹包,或者轉化爲完全揹包都是可以的。
解題報告:http://www.cnblogs.com/ziyi–caolu/p/3216827.html
4、poj2392(推薦)
題意:有k種石頭,高爲hi,在不超過ai的高度下,這種石頭可以放置,有ci種這個石頭,求這些石頭所能放置的最高高度………
思路:以往的什麼硬幣種數,最大硬幣數之類的,他們的硬幣都已經是排好序了的,總是從小到大,但是這個題目不同,它有着最高高度的限制,那麼在思考的時候,要得到最優的,那麼首先就是要對ai排序……這是貪心,然後就是多重揹包了……..

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
struct node
{
    int h;
    int a;
    int c;
}s[500];
int dp[50000],num[41000];
int cmp(const node p,const node q)
{
    return p.a<q.a;
}
int main()
{
    int n;
    while(scanf("%d",&n)>0)
    {
        int maxx=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d %d",&s[i].h,&s[i].a,&s[i].c);
            if(maxx<s[i].a)
            maxx=s[i].a;
        }
        sort(s+1,s+1+n,cmp);
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        for(int i=1;i<=n;i++)
        {
            memset(num,0,sizeof(num));
            for(int j=s[i].h;j<=maxx;j++)
            if(dp[j-s[i].h]&&dp[j-s[i].h]+s[i].h>dp[j]&&dp[j-s[i].h]+s[i].h-1<=s[i].a&&num[j-s[i].h]<s[i].c)
            {
                dp[j]=dp[j-s[i].h]+s[i].h;
                //printf("%d %d\n",dp[j],s[i].h);
                num[j]=num[j-s[i].h]+1;
                //if(dp[j]==37)
                //printf("%d\n",num[j]);
            }
        }
        int maxn=0;
        for(int i=0;i<=maxx;i++)
        if(maxn<dp[i])
        maxn=dp[i];
        printf("%d\n",maxn-1);
    }
    return 0;
}

多維揹包
1、hdu4501

思路:將v1,v2,k都當作一種體積,每種物品只能取一次,求max…….

反思:以前寫揹包,由於只有一個體積,所以習慣性的在for中,就所取的最小值限制,而在這次,因爲這裏導致wa了,具體是因爲在多個體積限制的揹包裏,當這個體積小於它的最小體積時,它可以不去減它的最小體積,而是作爲一種狀態來傳遞其他體積的限制的值…….
解題報告:http://www.cnblogs.com/ziyi–caolu/p/3217151.html
2、hdu2159
解題報告:http://www.cnblogs.com/ziyi–caolu/p/3222683.html
3、hdu3496
題意:給你n張電影門票,但一次只可以買m張,並且你最多可以看L分鐘,接下來是n場電影,每一場電影a分鐘,b價值,要求恰好看m場電影所得到的最大價值,要是看不到m場電影,輸出0;
思路:這個題目可以很明顯的看出來,有兩個限制條件,必須看m場電影的最大價值……..其實我前面在01揹包時提過,對於這樣的條件,要可以看第n場電影,那麼相對應的第n-1場電影必須看了,否則不能進行動態轉移…….我的想法是,0代表着這場電影沒有看,>0代表這場電影看了。其他的就是動態轉移了,很容易得到,dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+val[i])……..當然,在最開始的dp[0][0]=1,那麼得到的最大值會在第m場電影裏面,最大值需要減去初始值……..也就是1

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[105][1005],s[105][2];
int max(int x,int y)
{
    if(x>y)
    return x;
    else
    return y;
}
int main()
{
    int text;
    scanf("%d",&text);
    while(text--)
    {
        int N,M,L;
        scanf("%d %d %d",&N,&M,&L);
        for(int i=1;i<=N;i++)
        scanf("%d %d",&s[i][0],&s[i][1]);
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=N;i++)
        {
            for(int j=M;j>=0;j--)
            {
                for(int k=L;k>=0;k--)
                {
                    if(k>=s[i][0]&&j>0&&dp[j-1][k-s[i][0]]&&dp[j][k]<dp[j-1][k-s[i][0]]+s[i][1])
                    {
                        dp[j][k]=dp[j-1][k-s[i][0]]+s[i][1];
                    }
                }
            }
        }
        int i,maxn=0;
        for(i=0;i<=L;i++)
        if(dp[M][i]>maxn)
        maxn=dp[M][i];
        if(maxn==0)
        printf("0\n");
        else
        printf("%d\n",maxn-1);
    }
    return 0;
}

4、poj2576(推薦)
題意:有一羣sb要拔河,把這羣sb分爲兩撥,兩撥sb數只差不能大於1,輸出這兩撥人的體重,小的在前面……
思路:把總人數除2,總重量除2,之後你會發現就是個簡單的二維揹包,有兩個限制…..一個是人數,一個是體重,再仔細思考下,發現一定要有這麼多人,也就是說一定要有總人數除以2這麼多人,那麼當第n個人存在,第n-1個人必須存在………

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[55][23000],a[105];
int main()
{
    int n;
    while(scanf("%d",&n)>0)
    {
        int m=(n+1)/2,sum=0,maxx=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        if(n==1)
        {
            printf("0 %d\n",a[1]);
            continue;
        }
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                for(int k=sum/2;k>=0;k--)
                {
                    if(j>0&&k-a[i]>=0&&dp[j-1][k-a[i]]&&dp[j][k]<dp[j-1][k-a[i]]+a[i])
                    dp[j][k]=dp[j-1][k-a[i]]+a[i];
                    //printf("%d\n",dp[j][k]-1);
                    if(maxx<dp[j][k])
                    maxx=dp[j][k];
                }
            }
        }
        maxx--;
        //printf("%d\n",maxx);
        int tmp=sum-maxx;
        if(tmp<maxx)
        {
            int h=tmp;
            tmp=maxx;
            maxx=h;
        }
        printf("%d %d\n",maxx,tmp);
    }
    return 0;
}

5、poj1837(天平問題 推薦)
題意:給你c(2<=c<=20)個掛鉤,g(2<=g<=20)個砝碼,求在將所有砝碼(砝碼重1~~25)掛到天平(天平長 -15~~15)上,並使得天平平衡的方法數…….
思路:(這是我木有想到的)將g個掛鉤掛上的極限值:15*25*20==7500
那麼在有負數的情況下是-7500~~7500 以0爲平衡點……
那可以將平衡點往右移7500個單位,範圍就是0~~15000……這樣就好處理多了
其實我覺得以後的題目中不僅僅天平問題可以這樣處理,在有負數的以及要裝入數組處理的題目中,我們都可以嘗試着平移簡化問題……
這題目是要將所有的砝碼都掛到天平上後的最多方法數,同時砝碼自帶質量,也就是說,這不僅僅有着“容量”的限制,還有着“件數”的限制,很明顯的二維費用揹包……
每個砝碼只能用一次,果斷01揹包,並且在處理這一狀態前,先判斷前一狀態是否存在……我喜歡用>0表示存在,用0表示不存在,而這個題目又是求方法數,不需要再減去1……..

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[25][16000],s[25],t[25];
int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m)>0)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&s[i]);
        }
        for(int i=1;i<=m;i++)
        scanf("%d",&t[i]);
        memset(dp,0,sizeof(dp));
        dp[0][7500]=1;
        int sum=0;
        for(int i=1;i<=m;i++)             //m個砝碼 
        {
            for(int j=15000;j>=1;j--)    //01揹包,每個砝碼只能用一次 
            for(int k=1;k<=n;k++)
            if(j+s[k]*t[i]>=0&&j+s[k]*t[i]<=15000&&dp[i-1][j+s[k]*t[i]])   //判斷前一狀態是否存在........ 
            {
                dp[i][j]+=dp[i-1][j+s[k]*t[i]];
                //printf("j==%d   dp==%d   %d\n",j,dp[i][j],j+s[k]*t[i]);
            }
            //sum++;
        }
        printf("%d\n",dp[m][7500]);
    }
    return 0;
}

分組揹包
1、hdu1712

題意:有n門課程,和m天時間,完成a[i][j]得到的價值爲第i行j列的數字,求最大價值……

思路:分組揹包,就是第n門課程,可以做一天,可以做兩天,但它們相斥,你做了一天,就不能再做一天…也就是不能再做這門課程了……

(這裏少一個圖片 先貼着博主的地址 http://www.cnblogs.com/ziyi–caolu/p/3234919.html

當然這是最多取一個的算法…….

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[150],a[150][150];
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)>0&&(n+m))
    {
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        scanf("%d",&a[i][j]);
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)      分成n組 
        {
            for(int j=m;j>=1;j--)         體積 
            {
                for(int k=1;k<=m;k++)       第i組內的各個數據 
                if(j-k>=0&&dp[j]<dp[j-k]+a[i][k])
                dp[j]=dp[j-k]+a[i][k];
            }
        }
        printf("%d\n",dp[m]);
    }    
    return 0;
} 

2、hdu3033(最少取一次 推薦)
題意:有n雙鞋子,m塊錢,k個品牌,(一個品牌可以有多種價值不同的鞋子),接下來n種不同的鞋子,a爲所屬品牌,b爲要花費的錢,c爲所能得到的價值。每種價值的鞋子只會買一雙,有個人有個偉大的夢想,每個品牌的鞋子至少買一雙,問他這個夢想可以實現不?不可以實現輸出Impossible,可以實現輸出最大價值……
思路:很容易看出來這是個分組揹包題,當然這個分組揹包有些不同於每組最多取一個的分組揹包……但我是覺得,分組揹包就這麼幾種問法吧
1、最常見的、最水的,每組最多取1個………(一般是隱性的,需要自己分析)
2、每組至少取1個……..(就是本題)
3、隨意選,可以選,可以不選,可以只選1個,也可以選多個……(暫時還未學,馬上會學)…..
對於第一種,模板題,只要你可以分析出來,那麼可以水過…..
對於第二種,我想說也是模板題,當然是以本題爲基礎的模板………
好吧,這道題目,首先,每組至少取一個,就是說必須要取一個,那麼數組dp[i][j],代表的含義就是 前i組容量爲j的情況下所得到的最大價值爲dpi][j];
同樣的,我們首先思考它的狀態,每組必須要取一個,那麼第i組存在的情況下,第i-1組也必須存在,也是回到了前面所做揹包所說的那種“一定”、“必須”的狀態,那麼同樣的在動態轉移的時候,要判斷它的前一個狀態合不合法,我個人比較喜歡用0來判斷不合法,>0判斷合法…….初始化dp[0][0]=1,最後得到的結果減去1……我想說的是,最後的結果不一定會集中在dp[k][m]上,因爲這個狀態它不一定存在,也就是說,這個狀態不一定合法,當然,也沒有關係,我們考慮第k組一定要存在,那麼掃描下dp[k][i],取最大值就好…..

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<queue>
using namespace std;
int dp[15][10005],s[105][2],num[15][105];
queue<int>Q[105];
int main()
{
    int n,m,k;
    while(scanf("%d %d %d",&n,&m,&k)>0)
    {

          for(int i=0;i<105;i++)
          while(!Q[i].empty())
          Q[i].pop();
          for(int i=1;i<=n;i++)
          {
               int a,b,c;
               scanf("%d %d %d",&a,&b,&c);
               Q[a].push(i);
               s[i][0]=b;
               s[i][1]=c;
          }

          for(int i=1;i<=k;i++)
          {
               int j=1;
               while(!Q[i].empty())
               {
                    num[i][j++]=Q[i].front();
                    //printf("i==%d %d\n",i,Q[i].front()); 
                    Q[i].pop();
               }
               num[i][0]=j;
          }                                 //前面都是將數據處理好 
          memset(dp,0,sizeof(dp));       //初始化dp 
          dp[0][0]=1;
          int flag=1;
          for(int i=1;i<=k;i++)        //這是分組 
          {
              if(num[i][0]==1)        //要是有一組沒有數據,那麼說明,它不可能滿足每組必須取一個這條件....... 
              {
                    flag=0;
                    break;
              }
              for(int f=1;f<num[i][0];f++)     //不同於最多取一個的分組揹包,這裏是先放每組有的物品,後放容積....... 
              {
                   int xx=num[i][f];;
                   for(int j=m;j>=0;j--)        //至於爲什麼這麼放?我是認爲,它是一種模板....... 
                   {
                        if(j-s[xx][0]>=0&&dp[i][j-s[xx][0]]&&dp[i][j-s[xx][0]]+s[xx][1]>dp[i][j]) //這個判斷必須放到第一,以免重複 
                        dp[i][j]=dp[i][j-s[xx][0]]+s[xx][1];

                        if(j-s[xx][0]>=0&&dp[i-1][j-s[xx][0]]&&dp[i-1][j-s[xx][0]]+s[xx][1]>dp[i][j])//這個必須放在上一個判斷下面..... 
                        dp[i][j]=dp[i-1][j-s[xx][0]]+s[xx][1];

                   }
              }
          }
          int maxx=0;
          for(int i=0;i<=m;i++)
          if(maxx<dp[k][i])
          maxx=dp[k][i];
          if(maxx==0||flag==0)
          printf("Impossible\n");
          else
          printf("%d\n",maxx-1);//最大值記得減去1 
    }
    return 0;
}

3、hdu3535(必做,三種狀態都有:最少取一次,最多取一次,隨意取 推薦)
題意:有0,1,2三種任務,0任務中的任務至少得完成一件,1中的任務最多完成1件,2中的任務隨便做。每一個任務最多隻能做一次 。n代表有n組任務,t代表有t分鐘,m代表這組任務有m個子任務,s代表這m個子任務屬於0,1,2中的哪種類型,接下來是m個子任務,第一個數代表要花費的時間,第二個數代表得到的愉悅度……求在可以完成工作的情況的最大愉悅度….要是不能完成,輸出-1(題意要求每個子任務只能被取一次)
錯誤思路:我一開始想,把0,1,2這三大組任務的子任務先統計好,在dp的時候,我開dp[3][105],代表在完成3組任務體積爲105的情況的最大愉悅度…….這種思路是錯的,因爲題目給出的n組任務是有其固定順序,只能是按照它給出來的解決問題…….
ac思路:分爲n組,每一組判斷這一組是屬於0,1,2三種任務中的哪一組……
若是屬於0,那麼將這一組的dp[i][j],j從0~~t全部置爲負無窮大,然後開始動態轉移…..爲什麼要置爲負無窮大?因爲只有這樣才能不出現一個都不選擇的情況…..其動態轉移方程,,在我前一個分組揹包題目已經詳細推導過,就是dp[i][j]=max(dp[i][j-v[i]]+val[i],dp[i-1][j-v[i]]+val[i],dp[i][j])
若是屬於1,先將第i-1的狀態傳遞到第i狀態,在開始分組揹包的模板……最多取一個dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+val[i]);
若是屬於2,先將第i-1的狀態傳遞到第i狀態,隨意取,那麼可以不取,取任意個子任務……dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+val[i],dp[i-1][j-v[i]]+val[i]);

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
#define M -1000000000
int dp[105][105],s[105][2];
int main()
{
    int n,t;
    while(scanf("%d %d",&n,&t)>0)
    {
        int m,k;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d",&m,&k);
            for(int j=1;j<=m;j++)
            scanf("%d %d",&s[j][0],&s[j][1]);
            if(k!=0)
            for(int j=0;j<=t;j++)        // 傳遞狀態 
            dp[i][j]=dp[i-1][j];
            if(k==0)                        //至少取一個 ,分組揹包變形模板....... 
            {
                for(int tmp=0;tmp<=t;tmp++) 
                dp[i][tmp]=M;
                for(int tmp=1;tmp<=m;tmp++)  //這層for循環必須在前面...... 
                {
                    for(int j=t;j>=0;j--)             
                    {
                        if(j-s[tmp][0]>=0&&dp[i][j-s[tmp][0]]+s[tmp][1]>dp[i][j])
                        dp[i][j]=dp[i][j-s[tmp][0]]+s[tmp][1];

                        if(j-s[tmp][0]>=0&&dp[i-1][j-s[tmp][0]]+s[tmp][1]>dp[i][j])
                        dp[i][j]=dp[i-1][j-s[tmp][0]]+s[tmp][1];
                    }
                }
            }
            else if(k==1)               //最多取一個 ,分組揹包模板 
            {
                for(int j=t;j>=0;j--)
                {
                    for(int tmp=1;tmp<=m;tmp++)
                    {    
                        if(j-s[tmp][0]>=0)
                        {
                            if(dp[i-1][j-s[tmp][0]]+s[tmp][1]>dp[i][j])
                            dp[i][j]=dp[i-1][j-s[tmp][0]]+s[tmp][1];
                        }    
                    }
                }
            }
            else if(k==2)                     //隨意取 ,01揹包,每個子任務只能取一次,卻可以取任意個不同的子任務 
            {
                for(int tmp=1;tmp<=m;tmp++)
                {
                    for(int j=t;j>=s[tmp][0];j--)
                    {
                        if(dp[i][j-s[tmp][0]]+s[tmp][1]>dp[i][j])
                        dp[i][j]=dp[i][j-s[tmp][0]]+s[tmp][1];

                        if(dp[i-1][j-s[tmp][0]]+s[tmp][1]>dp[i][j])
                        dp[i][j]=dp[i-1][j-s[tmp][0]]+s[tmp][1];

                    }
                }
            }
        }
        if(dp[n][t]<0)
        printf("-1\n");
        else
        printf("%d\n",dp[n][t]);
    }
    return 0;
}

混合揹包
總得來說,混合揹包很容易理解,就是有的物品只能取一次,有的物品能取無限次,有的物品取得次數有限制,這樣的話分別對對應情況採用01、完全、多重揹包即可。但是題目往往沒有那麼簡單、赤裸裸的,讓你一看就知道是混合揹包的,比如說下面這道題:
poj1742

題意:給你價值爲a1,a2…..的貨幣,每種有c1,c2…….個,求這些貨幣所能組成的價值小於等於m有多少個…..

思路:很像一道多重揹包題?那我一開始的確是用多重揹包的思路編寫的……TLE了,原來其中隱藏着一個被我忽視的一個問題,當ai*ci>=m時,我們沒有必要去拆分ci了,就直接把這種情況當作完全揹包處理…….

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[100005],t[300][2];
int n,m;
int sum;
void deal1(int x)
{
        for(int j=m;j>=x;j--)
        if(dp[j-x]&&dp[j-x]+x>dp[j]&&dp[j-x]+x-1<=m)
        {
                if(!dp[j])   sum++;
                dp[j]=dp[j-x]+x;
        }
}
int main()
{

      while(scanf("%d%d",&n,&m)>0&&(n+m))
      {
           for(int i=1;i<=n;i++)
           {
                   scanf("%d",&t[i][0]);
           }
           memset(dp,0,sizeof(dp));
           dp[0]=1;
           sum=0;
           for(int i=1;i<=n;i++)
           {
                   scanf("%d",&t[i][1]);
                   if(t[i][1]*t[i][0]<m)
                   {
                        int k=1;
                        while(t[i][1]-k>0)
                        {
                            deal1(k*t[i][0]);
                            t[i][1]-=k;
                            k*=2;
                        }
                        deal1(t[i][1]*t[i][0]);
                    }
                    else
                    {
                        for(int j=t[i][0];j<=m;j++)
                        if(dp[j-t[i][0]]&&dp[j]<dp[j-t[i][0]]+t[i][0])
                        {
                            if(!dp[j])   sum++;
                            dp[j]=dp[j-t[i][0]]+t[i][0];
                        } 
                    }
           }
           printf("%d\n",sum);
      }
      return 0;
}

此次的揹包總結到此結束了,我是做完區間dp纔來寫這個總結的,dp說起來是很神奇,但是要是你可以推導出狀態,根據狀態設置好初始化,那麼就不算是難……

朋友們,雖然這個世界日益浮躁起來,只要能夠爲了當時純粹的夢想和感動堅持努力下去,不管其它人怎麼樣,我們也能夠保持自己的本色走下去。

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