算法复习——背包dp

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类的问题吧···其实背包问题并不复杂,充分熟悉每一种背包的特性,每次分析出问题的背包组成类型(如上题),然后配上相应的思想和代码即可

时间: 2024-12-22 11:24:41

算法复习——背包dp的相关文章

算法复习——区间dp

感觉对区间dp也不好说些什么直接照搬讲义了2333 例题: 1.引水入城(洛谷1514) 这道题先开始看不出来到底和区间dp有什么卵关系···· 首先肯定是bfs暴力判一判可以覆盖到哪些城市····无解直接输出···有解得话就要想想了···· 这道题关键是要发现··如果一个蓄水池所在城市可以覆盖到一些沙漠城市···那么这些沙漠城市肯定是一段····不然假设有一个城市是断开的而两边都被同一个蓄水池流出的水覆盖,这个城市四周的城市都肯定比它矮···(不理解举个反例吧···反正我举不出来)···然后就

算法复习计划

写在前面 随着四月的到来, 离省选越来越近了. 从NOIP到现在, 学到了很多很多东西, 有的学的比较深入, 有的只是略知一二 从明天开始, 进行针对省选的算法复习计划. 省选前完成. 重点是对算法的理解和应用, 还会注重模板习惯的养成 计划内容 1. 数据结构 一直觉得我数据结构学的还可以, 不过列出来发现会的也没多少. 少就少吧, 省选够用就行... 线段树 树状数组 并查集 哈希表 STL treap splay 树链剖分 主席树(可忽略) 字符串(KMP, 后缀数组) 2. 图论 掌握经

hdu 2844 混合背包【背包dp】

http://acm.hdu.edu.cn/showproblem.php?pid=2844 题意:有n种纸币面额(a1,a2,...an),每种面额对应有(c1,c2,...cn)张.问这些钱能拼成1-m中多少种值. 题解:背包dp问题.若ci=1,是01背包,若ci*ai>=m则是完全背包,否则是多重背包.(详见<背包九讲>) 先复习一下三种简单背包形式: 01背包(F[v] ← max{F[v], F[v ?Ci] +Wi} ): 完全背包(F[i, v] = max(F[i ?

HAOI2010 软件安装 有依赖的背包DP

题目描述 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一 些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大). 但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j).幸运的 是,一个软件最多依赖另外一个软件.如果一个软件不能正常工作,那么它能够发挥的作用为0. 我们现在知道了软件之间的依赖关系:软件i依赖软件Di.现在请你设计出

[BZOJ 1025] 游戏 置换群 背包DP

题意 对于一个 $n$ 阶置换群 $A$ , 它的循环节大小分别为 $a_1, a_2, ..., a_m$ , 则有 $\sum_{i = 1} ^ m a_i = n$ . 定义 $f(A)$ 为它的所有循环节的最小公倍数, 即 $f(A) = [a_1, a_2, ..., a_m]$ . 求在所有 $n$ 阶置换群中, $f(A)$ 有多少种取值. $n \le 1000$ . 分析 判断 $K$ 可不可取. $K = \prod_{i = 1} ^ r {s_r} ^ {t_r}$ 可

hdu 5234 Happy birthday 背包 dp

Happy birthday Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=5234 Description 今天是Gorwin的生日.所以她的妈妈要实现她的一个愿望.Gorwin说她想吃很多蛋糕.所以他妈妈带她来到了蛋糕园. 这个园子被分成了n*m个方格子.在每一个格子里面,有一个蛋糕.第i行,第j列的格子中有一个重量为wij千克的蛋糕,Gorwin从左上角(1,1

hdu 1171 Big Event in HDU(背包DP)

题意: 杭电搬迁,有N种设备,每种设备有个价值V,数量M,要求将这些设备平分,使得平分后两边的总价值尽可能地相等. 输出两边各自的总价值. 思路: 背包DP后,P=所有的总价值/2,然后从P开始往两边找到第一个满足的价值. 可以降维,但是要注意for循环的顺序. 看代码. 代码: int v[55], m[55]; bool dp[250005]; int main(){ int n; while(scanf("%d",&n)!=EOF && n>=0){

POJ 1384 Piggy-Bank 背包DP

所谓的完全背包,就是说物品没有限制数量的. 怎么起个这么intimidating(吓人)的名字? 其实和一般01背包没多少区别,不过数量可以无穷大,那么就可以利用一个物品累加到总容量结尾就可以了. 本题要求装满的,故此增加个限制就可以了. #include <stdio.h> #include <stdlib.h> #include <string.h> inline int min(int a, int b) { return a < b? a : b; } c

BZOJ 1042 硬币购物(完全背包+DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1042 题意:给出四种面值的硬币c1,c2,c3,c4.n个询问.每次询问用d1.d2.d3.d4个相应的硬币能够拼出多少种总和为s? 思路:(1)首先,用完全背包求出f[i]表示四种硬币的数量无限制拼出i的方案数. (2)接着我们来理解 x=f[s]-f[s-(d1+1)*c1]的含义:x表示c1硬币的数量不超过d1个而其他三种硬币的数量不限制拼成s的方案数.我们举着例子来说明, 假设