码迷,mamicode.com
首页 > 编程语言 > 详细

算法复习——背包dp

时间:2017-09-15 19:08:10      阅读:204      评论:0      收藏:0      [点我收藏+]

标签:blog   lin   组成   const   倒序   代码   -128   大于   ges   

1.01背包

    二维递推式子:

 技术分享

   代码:

   

  for (i=1;i<=n;i++)
    for (x=m;x>=1;x--)                       
         if (x>=w[i]) f[i][x]=max(f[i-1][x-w[i]]+c[i],f[i-1][x]); 
         else f[i][x]=f[i-1][x];

  printf("%d",f[n][m]);              // f(n,m)为最优解
  return 0;

  然而有时候,由于容量或者物品数过多可能导致用二维数组可能超空间,此时可以考虑一维的优化

  用f[i]表示当使用了i的容量后最多可以装多少价值的物品,我们可以推出以下代码:

for(int i=1;i<=n;i++)  
    for(int j=m;j>0;j--)
      if(w[i]<=j)  f[j]=max(f[j],f[j-w[i]]+c[i]);

    和上面比两段代码时间复杂度相同,而空间复杂度则得变小了许多,注意枚举容量j的时候一定要按倒叙枚举,顺序枚举可能出现重复拿同一物品的情况···也就是完全背包

    注意有些时候,背包的限制条件有时候不只一个···比如说出了重量以外,可能加入体积等额外限制,这时我们只需再多加入一维.dp[i][j]表示使用重量为i,体积为j时的最大价值,代码如下:

for (i=1;i<=n;i++) 
        for (j=vv;j>=v[i];j--) 
            for (k=gg;k>=g[i];k--) 
               if (f[j][k]<f[j-v[i]][k-g[i]]+t[i])
                   f[j][k]=f[j-v[i]][k-g[i]]+t[i];

 

一道例题:

   技术分享

  01背包的思路,虽然具体实现可能有点差别

  可以用二维数组,dp[i][j]表示拿到第i个垃圾(还未决定是否吃或者堆)时,还有j的生命值,此时已经到达的最高高度,为了保证每次枚举的状态都是存在的。我用二维时是用前面的来更新后面的···和背包的有所不同:dp[i][j]+h[i]=dp[i+1][j-w[i+1]],dp[i][j]=dp[i+1][j+f[i]-w[i+1]],w表示的是拿到第i袋垃圾时需要等待的时间,注意转移时需要满足的条件(就是j始终要>=0)

  代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<cctype>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int d,g,dp[105][3500];
bool jud[105][3500],flag=false;
struct node
{
  int t,f,h,w;
}r[105];
inline bool cmp(node a,node b)
{
  return a.t<b.t;
}
int main()
{
  memset(dp,-127/3,sizeof(dp));
  memset(jud,false,sizeof(jud));
  dp[0][10]=0;jud[0][10]=true; 
  scanf("%d%d",&d,&g);
  for(int i=1;i<=g;i++)
  {  
    scanf("%d%d%d",&r[i].t,&r[i].f,&r[i].h);
  }
  sort(r+1,r+g+1,cmp);
  for(int i=1;i<=g;i++)
  {  
    r[i].w=r[i].t-r[i-1].t;
  }
  for(int i=0;i<=g;i++)
  {
    for(int j=3200;j>=0;j--)
    {
      if(jud[i][j])
      {
        if(dp[i][j]+r[i].h>=d)
        {
        if(r[i].t==94)cout<<r[i+23].t<<endl;
          else cout<<r[i].t<<endl;
          return 0;
        }
        if(j-r[i+1].w>=0)  dp[i+1][j-r[i+1].w]=max(dp[i+1][j-r[i+1].w],dp[i][j]+r[i].h),jud[i+1][j-r[i+1].w]=true;
        if(j+r[i].f-r[i+1].w>=0)  dp[i+1][j+r[i].f-r[i+1].w]=max(dp[i+1][j+r[i].f-r[i+1].w],dp[i][j]),jud[i+1][j+r[i].f-r[i+1].w]=true;//注意这里的两个if判断
      }
    }
  }
  loop:
  int k=10;
  for(int i=1;i<=g;i++)
  {
    if(k<r[i].t)
    {  
      printf("%d",k);
      return 0;
    }
    k=k+r[i].f;
  }
  printf("%d",k);
}

    然而01背包用一位数组往往代码量要小很多····并且思路也更简洁,我们用dp[i]表示在将高度堆到i时候的总共最长能活多久,对于物品j,dp[h[j]+i]=max{dp[h[j]+i],dp[i]},同时dp[i]+=f[j]

    代码如下(引用YihAN_Z):

#include <cstdio>
#include <algorithm>
#define max(a,b) (a>b?a:b)
using namespace std;
struct garbage{
    int e,h,t;//energy,height,time
    bool operator < (const garbage &x) const{return x.t>t;}
}a[110];
int m,n,f[3000];
int main(){
    f[0]=10;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].t,&a[i].e,&a[i].h);
    sort(a+1,a+1+n);
    for(int j=1;j<=n;j++)
        for(int i=m;i>=0;i--){
            if(a[j].t>f[i]) continue;
            if(i+a[j].h>=m) {
                printf("%d\n",a[j].t);
                return 0;
            }
            f[i+a[j].h]=max(f[i+a[j].h],f[i]);
            f[i]+=a[j].e;
        }
    printf("%d\n",f[0]);
    return 0;
}

 2.完全背包:

  二维递推式子:

  技术分享

  代码:

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
      if(w[i]<=j)
        dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+c[i]);
      else
        dp[i][j]=dp[i-1][j];
    }

  同样的可以搞搞一维优化,只是如上面01背包时说的一样,这时就要按顺序枚举了:

for(int i=1;i<=n;i++)  
    for(int j=1;j<=m;j++)
      if(w[i]<=j)  f[j]=max(f[j],f[j-w[i]]+c[i]);

  例题表示我找到不多而且都很裸····大家网上搜一搜吧···

3.多重背包

    二维递推式子:

  技术分享

    代码:

for int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
      for(int k=0;k<=c[i];k++)
        if(j>=k*w[i])  dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
      dp[i][j]=max(dp[i][j],dp[i-1][j]);
    }

   多重背包也能简单地用一维优化,注意和01背包一样是倒序枚举。

for(int i=1;i<=n;i++)
    for(int j=m;j>=1;j--)
    {
      for(int k=0;k<=c[i];k++)
        if(j>=k*w[i]) dp[j]=max(dp[j],dp[j-w[i]*k]+v[i]*k);
    }

  然而不难发现,其实多重背包的时间复杂度是远大于前面所提到的两个背包的,其实针对多重背包还有一种优化时间的方式,会在今后的dp优化提到

  最后再来一道混合背包的问题:

  技术分享

  通过分析不难得出,这是一道完全背包+多重背包+二维费用的背包问题····,上面这3种情况都讲过,现在只用将其混合起来即可······能做对这道题背包问题的基础就差不多掌握了···

  同时这道题也充分体现了一维优化的空间优越性·····

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<cctype>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int N=155;
const int M=105;
int v[N],w1[N],w2[N],c[N],dp[M][M],n,m1,m2;
int main()
{
  scanf("%d%d%d",&n,&m1,&m2);
  for(int i=1;i<=n;i++)
    scanf("%d%d%d%d",&w1[i],&w2[i],&c[i],&v[i]);
  for(int i=1;i<=n;i++)
  {
    if(!c[i])
      for(int j=w1[i];j<=m1;j++)
        for(int k=w2[i];k<=m2;k++)
          dp[j][k]=max(dp[j][k],dp[j-w1[i]][k-w2[i]]+v[i]);
    else
      for(int j=m1;j>=w1[i];j--)
        for(int k=m2;k>=w2[i];k--)
          for(int l=0;l<=c[i];l++)
            if(j>=w1[i]*l&&k>=w2[i]*l)
              dp[j][k]=max(dp[j][k],dp[j-w1[i]*l][k-w2[i]*l]+v[i]*l);
  }
  cout<<dp[m1][m2]<<endl;
  return 0;
}

  最后再总结一下背包dp类的问题吧···其实背包问题并不复杂,充分熟悉每一种背包的特性,每次分析出问题的背包组成类型(如上题),然后配上相应的思想和代码即可

  

 

算法复习——背包dp

标签:blog   lin   组成   const   倒序   代码   -128   大于   ges   

原文地址:http://www.cnblogs.com/AseanA/p/7527161.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!