dp之背包总结篇

来源:互联网 发布:淘宝整机烈士墙2016 编辑:程序博客网 时间:2024/06/06 20:15

前言:背包问题在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 1000005int 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:ytypedef 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 -1000000000int 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说起来是很神奇,但是要是你可以推导出状态,根据状态设置好初始化,那么就不算是难……

朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。