DP专题->01背包

首先01背包题目的雏形是

N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

从这个题目中可以看出,01背包的特点就是:每种物品仅有一件,可以选择放或不放。

其状态转移方程是:

f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

对于这方方程其实并不难理解,方程之中,现在需要放置的是第i件物品,这件物品的体积是c[i],价值是w[i],因此f[i-1][v]代表的就是不将这件物品放入背包,而f[i-1][v-c[i]]+w[i]则是代表将第i件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。

理解了这个方程后,将方程代入实际题目的应用之中,可得

for(i = 1; i<=n; i++)
{
    for(j = v; j>=c[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了
    {
        f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);
    }
}

理解了01背包之后,下面就来看看实际的题目:


HDU2546:饭卡

http://acm.split.hdu.edu.cn/showproblem.php?pid=2546
很经典的一道01背包题,要注意的是这里只要剩余的钱不低于5元,就可以购买任何一件物品,所以5在这道题中是很特许的,再使用01背包之前,我们首先要在现在所拥有的余额中保留5元,用这五元去购买最贵的物品,而剩下的钱就是背包的总容量,可以随意使用,因此可得代码

#include <stdio.h>
#include <algorithm>
using namespace std;

int cmp(int a,int b)
{
    return a<b;
}

int main()
{
    int n;
    while(~scanf("%d",&n),n)
    {
        int i,price[2013]= {0},dp[2013] = {0};
        for(i = 1; i<=n; i++)
            scanf("%d",&price[i]);
        sort(price+1,price+1+n,cmp);
        int MAX=price[n];
        int j,m;
        scanf("%d",&m);
        if(m<5)//低于5元不能购买
        {
            printf("%d\n",m);
            continue;
        }
        m-=5;//取出5元用于购买最贵的物品
        for(i = 1; i<n; i++)//01背包
        {
            for(j = m;j>=price[i];j--)
            {
                dp[j] = max(dp[j],dp[j-price[i]]+price[i]);
            }
        }
        printf("%d\n",m+5-dp[m]-MAX);
    }

    return 0;
}


HDU2602:Bone Collector

http://acm.split.hdu.edu.cn/showproblem.php?pid=2602

经典的01背包题,给出了石头的数量与背包的容量,然后分别给出每个石头的容量与价值,要求最优解,经过前面的练手,这道题已经是很简单了,可以说是01背包裸题。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

struct Node
{
    int h;
    int v;
} node[1005];

int main()
{
    int t,n,m,l;
    int dp[1005];
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        int i;
        for(i = 1; i<=n; i++)
            scanf("%d",&node[i].h);
        for(i = 1; i<=n; i++)
            scanf("%d",&node[i].v);
        memset(dp,0,sizeof(dp));
        for(i = 1; i<=n; i++)
        {
            for(l = m; l>=node[i].v; l--)
                dp[l] = max(dp[l],dp[l-node[i].v]+node[i].h);
        }
        printf("%d\n",dp[m]);
    }

    return 0;
}


HDU2639:Bone Collector II(01背包第k优解)

http://acm.split.hdu.edu.cn/showproblem.php?pid=2639

解决了上面那倒题目之后,这道题跟上面的题目有些不同,因为这里要求的是第K优解

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

struct Node
{
    int price;
    int val;
} node[1005];

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,v,k,i,dp[1005][31] = {0},a[31],b[31];
        scanf("%d%d%d",&n,&v,&k);
        for(i = 0; i<n; i++)
            scanf("%d",&node[i].price);
        for(i = 0; i<n; i++)
            scanf("%d",&node[i].val);
        int j;
        for(i = 0; i<n; i++)
        {
            for(j = v; j>=node[i].val; j--)
            {
                int cnt = 0,d;
                for(d = 1; d<=k; d++)//分别将放入第i个石头与不放第i个石头的结果存入a,b,数组之中
                {
                    a[d] = dp[j-node[i].val][d]+node[i].price;
                    b[d] = dp[j][d];
                }
                int x,y,z;
                x = y = z = 1;
                a[d] = b[d] = -1;
                while(z<=k && (x<=k || y<=k))//循环找出前K个的最优解
                {
                    if(a[x] > b[y])
                    {
                        dp[j][z] = a[x];
                        x++;
                    }
                    else
                    {
                        dp[j][z] = b[y];
                        y++;
                    }
                    if(dp[j][z]!=dp[j][z-1])
                    z++;
                }
            }
        }
        printf("%d\n",dp[v][k]);
    }

    return 0;
}


HDU1171:Big Event in HDU(多重背包)

http://acm.split.hdu.edu.cn/showproblem.php?pid=1171

这道题咋看有点复杂,其实也只是换了一种思维,因为题目要求要尽量平均分配,所以我们可以先将总价值sum求出,然后得出其分配的平均值为sum/2,要注意这个答案可能为小数,但是又因为sum是整数,所以最后得出的sum/2是要小于等于实际的值。将这个结果进行01,背包,可以得出其中一个宿舍所得的最大价值,而另一个宿舍的最大价值也可以相应的得到,而前者必定小于等于后者。

#include<bits/stdc++.h>
using  namespace std;

int val[5005];
int dp[255555];

int  main(){
  int n,sum,cnt;
  while(~scanf("%d",&n),n>0){
    memset(val, 0, sizeof(val));
    memset(dp, 0, sizeof(dp));
    sum=0;cnt=0;
    int a,b;
    for(int i=1; i<=n; i++){
      scanf("%d%d",&a,&b);
      while(b--){
        val[cnt++]=a;//将价值存入数组
        sum+=a;
      }
    }
    for(int i=0; i<cnt; i++){
      for(int j=sum/2; j>=val[i]; j--){  //01背包
        dp[j]=max(dp[j], dp[j-val[i]]+val[i]);
      }
    }
    printf("%d %d\n",sum-dp[sum/2],dp[sum/2]);
  }
  return 0;
}

HDU2955:Robberies

http://acm.split.hdu.edu.cn/showproblem.php?pid=2955

这道题有点特别,咋看之下其状态转移方程似乎有些不同,但事实上原理是相通的,每一家银行的钱看做重量,不被抓的概率看成价值,要注意其精度。

#include<bits/stdc++.h>
#define eps 1e-9
using namespace std;

struct node{
  int money;
  double p;
}bank[111];

int main(){
  int t,n;
  double p;
  scanf("%d",&t);
  while(t--){
    scanf("%lf%d",&p,&n);
    p=1.0-p;
    int sum=0;
    for(int i=1; i<=n; i++){
      scanf("%d%lf",&bank[i].money,&bank[i].p);
      bank[i].p=1.0-bank[i].p;
      sum+=bank[i].money;
    }
    double dp[11111]={1.0};//除dp[0]=1.0外,其他都=0.0
    for(int i=1; i<=n; i++){
      for(int j=sum; j>=bank[i].money; j--){
        dp[j]=max(dp[j], dp[j-bank[i].money]*bank[i].p);
      }
    }
    for(int i=sum; i>=0; i--){
        if(dp[i]-p>eps){
          printf("%d\n",i);
          break;
        }
    }
  }
  return 0;
}

HDU3466:Proud Merchants

http://acm.split.hdu.edu.cn/showproblem.php?pid=3466

这道题由于规定了手上的前低于q时就不能购买该样东西,所以要先将商品按q-p排序,剩下的就是简单的01背包了

原因:

for(int i=1; i<=n; i++){
      for(int j=m; j>=a[i].q; j--){
        dp[j]=max(dp[j], dp[j-a[i].p]+a[i].v);
要保证动归方程无后效性
j-a[i].p一定要比j先算
算i时,最小能算到a[i].q-a[i].p
因此以a[i].q-a[i].p从小到大排序
#include <bits/stdc++.h>
using  namespace  std;

struct  node{
  int p,q,v;
}a[555];
int dp[5555];

bool cmp(node x,node y){
  return x.q-x.p<y.q-y.p;//按q-p排序,保证差额最小为最优
}

int  main(){
  int n,m;
  while(~scanf("%d%d",&n,&m)){
    for(int i=1; i<=n; i++){
      scanf("%d%d%d",&a[i].p,&a[i].q,&a[i].v);
    }
    sort(a+1, a+n+1, cmp);
    memset(dp, 0, sizeof(dp));
    for(int i=1; i<=n; i++){
      for(int j=m; j>=a[i].q; j--){//剩余的钱大于q才能买
        dp[j]=max(dp[j], dp[j-a[i].p]+a[i].v);//这里的j-a[i].p决定了之前的排序方法
      }
    }
    printf("%d\n",dp[m]);
  }
  return 0;
}

HDU1864:最大报销额  

http://acm.split.hdu.edu.cn/showproblem.php?pid=1864

题目中要注意的有几样,首先每张发票中单件物品价格不能超过600,其次发票总额不能超过1000,而且发票上的物品必须是ABC三类,将满足以上条件的发票存入数组之中,就是裸01背包

#include <bits/stdc++.h>
using  namespace  std;
int dp[3333333],money[33];

int  main(){
  int sum,n;
  double x,y;
  while(~scanf("%lf%d",&x,&n),n){
    sum=(int)(x*100);
    memset(dp, 0, sizeof(dp));
    memset(money, 0, sizeof(money));
    char ch;
    int k,cnt=0;
    for(int i=1; i<=n; i++){
      scanf("%d",&k);
      int a=0,b=0,c=0,flag=1;;
      while(k--){
        scanf(" %c:%lf",&ch,&y);
        int v=(int)(y*100);
        if(ch=='A' && a+v<=60000)a+=v;
        else if(ch=='B' && b+v<=60000)b+=v;
        else if(ch=='C' && c+v<=60000)c+=v;
        else flag=0;
      }
      if(a+b+c<=100000 && flag)//按题意所说,必须满足这些条件
        money[cnt++]=a+b+c;
    }
    for(int i=0; i<cnt; i++){
      for(int j=sum; j>=money[i]; j--){
        dp[j]=max(dp[j], dp[j-money[i]]+money[i]);
      }
    }
    printf("%.2lf\n",dp[sum]/100.0);
  }
  return 0;
}

暂时更新到这里~






  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值