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.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;  
}  

HDU1171:Big Event in HDU

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

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


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

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

int main()  
{  
    int n,i,j,a,b,l,sum;  
    while(~scanf("%d",&n),n>0)  
    {  
        memset(val,0,sizeof(val));  
        memset(dp,0,sizeof(dp));  
        l = 0;  
        sum = 0;  
        for(i = 0;i<n;i++)  
        {  
            scanf("%d%d",&a,&b);  
            while(b--)  
            {  
                val[l++] = a;//将价值存入数组  
                sum+=a;  
            }  
        }  
        for(i = 0;i<l;i++)  
        {  
            for(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;  
}  

HDU2602:Bone Collector

http://acm.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.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;  
}  

HDU2955:Robberies

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

这道题有点特别,咋看之下其状态转移方程似乎有些不同,但事实上远离是相通的,要注意其精度


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

struct Bank  
{  
    int money;  
    double p;  
} bank[10005];  

int main()  
{  
    int n,t;  
    double p;  
    scanf("%d",&t);  
    while(t--)  
    {  
        scanf("%lf%d",&p,&n);  
        p = 1-p;  
        int i,j,sum = 0;  
        for(i = 0; i<n; i++)  
        {  
            scanf("%d%lf",&bank[i].money,&bank[i].p);  
            bank[i].p = 1-bank[i].p;  
            sum+=bank[i].money;  
        }  
        double dp[10005]= {1.0};  
        for(i = 0; i<n; i++)  
        {  
            for(j = sum; j>=bank[i].money; j--)  
            {  
                dp[j] = max(dp[j],dp[j-bank[i].money]*bank[i].p);  
            }  
        }  
        for(i = sum; i>=0; i--)  
        {  
            if(dp[i]-p>0.000000001)  
            {  
                printf("%d\n",i);  
                break;  
            }  
        }  
    }  

    return 0;  
}  

HDU3466:Proud Merchants

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

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

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

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

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

int main()  
{  
    int n,m,i,j;  
    int dp[5555];  
    while(~scanf("%d%d",&n,&m))  
    {  
        for(i = 0; i<n; i++)  
            scanf("%d%d%d",&a[i].p,&a[i].q,&a[i].v);  
        memset(dp,0,sizeof(dp));  
        sort(a,a+n,cmp);  
        for(i = 0; i<n; i++)  
        {  
            for(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.hdu.edu.cn/showproblem.php?pid=1864

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

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

int dp[3000050];//由于每张发票不超过1000,最多30张,扩大100倍数后开这么大即可  

int main()  
{  
    char ch;  
    double x,y;  
    int sum,a,b,c,money[35],v;  
    int t,i,j,k;  
    while(~scanf("%lf%d",&x,&t),t)  
    {  
        sum = (int)(x*100);//将小数化作整数处理  
        memset(money,0,sizeof(money));  
        memset(dp,0,sizeof(dp));  
        int l = 0;  
        for(i = 0; i<t; i++)  
        {  
            scanf("%d",&k);  
            a = b = c = 0;  
            int flag = 1;  
            while(k--)  
            {  
                scanf(" %c:%lf",&ch,&y);  
                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 && a<=60000 && b<=60000 && c<=60000 && flag)//按题意所说,必须满足这些条件  
                money[l++] = a+b+c;  
        }  
        for(i = 0; i<=l; i++)  
        {  
            for(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;  
}  

SDUT3358
http://acm.sdut.edu.cn/onlinejudge2/index.php/Solution/submitsolution/pid/3358.html

高数Umaru系列(9)——哈士奇

Time Limit: 1000MS Memory Limit: 65536KB
Submit Statistic

Problem Description
由于高数巨养的喵星人太傲娇了,要天天吃新鲜猫粮而且还经常欺负高数巨,所以高数巨决定买几条哈士奇尝尝鲜。这天高数巨来到了二手狗市场买哈士奇,高数巨看完了所有的哈士奇,记下了每条哈士奇的价格,并根据对它们的好感程度给它们每只都赋予了一个萌值。高数现在手里有X元,她想通过购买若干条哈士奇来获得尽可能多的萌值。现在给定高数巨手里的钱X以及N条哈士奇的价格和萌值,求高数巨最多可获得多少萌值

Input
多组输入。
对于每组输入,第一行有两个整数N,X(1 < = N < = 100,1 < = X < = 1000),分别表示哈士奇的数量和高数巨的钱数
接下来的N行每行有两个整数Pi,Mi(1 < = Pi,Mi < = 100),分别表示第i条哈士奇的价格和萌值

Output
对于每组数据,输出一个整数,表示高数巨最多可以获得的萌值,每组输出占一行

Example Input
2 100
50 20
60 40
3 100
20 55
20 35
90 95
1 10
20 50

Example Output
40
95
0

Hint

Author
Shannon


01背包问题,是用来介绍动态规划算法最经典的例子,网上关于01背包问题的讲解也很多,我写这篇文章力争做到用最简单的方式,最少的公式把01背包问题讲解透彻。

01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }
f[i,j]表示在前i件物品中选择若干件放在承受能力为j的背包中,可以取得的最大价值。
Pi表示第i件物品的价值。
决策:为了背包中物品总价值最大化,第 i件物品应该放入背包中吗 ?

对于这个状态转移方程来说,通俗易懂的暴力解释就是,现在有一个价格为2的物品,你手里有一个承受能力为8的背包,并且已经装满了,那么我怎么知道此摆在我面前的价格为2的物品能不能增加我的背包内的价值呢?
很简单,这个时候我们就应该找到,当前背包重量减去2时,也就是8-2 = 6时的最大价值,看看加上眼前物品是否比原来大,大就装上,不大就放弃。(可能现在不太理解,后面还会提到)

题目描述:

有编号分别为a,b,c,d,e的五只哈士奇,它们的价格分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承受能力为10的背包,如何让背包里装入的哈士奇具有最大的价值总和?

这里写图片描述
只要你能通过找规律手工填写出上面这张表就算基本理解了01背包的动态规划算法。
首先要明确这张表是至底向上,从左到右生成的

为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承受能力为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。
对于d2单元格,表示只有物品e,d时,承受能力为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。
同理,c2=0,b2=3,a2=6。
对于承受能力为8的背包,a8=15,是怎么得出的呢?
根据01背包的状态转换方程,需要考察两个值,

一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1,j-Wi]+Pi;

在这里,

f[i-1,j]表示我有一个承受能力为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值

f[i-1,j-Wi]也就是指单元格b6,值为9,Pi指的是a物品的价值,即6

由于f[i-1,j-Wi]+Pi = 9 + 6 = 15 大于f[i-1,j] = 9,所以物品a应该放入承受能力为8的背包

这个时候我们再来回头看上面状态转移方程的暴力解释
f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }

对于这个状态转移方程来说,通俗易懂的暴力解释就是,现在有一个价格为2的物品,你手里有一个承受能力为8的背包,并且已经装满了,那么我怎么知道此摆在我面前的价格为2的物品能不能增加我的背包内的价值呢?
很简单,这个时候我们就应该找到,当前背包重量减去2时,也就是8-2 = 6时的最大价值,看看加上眼前物品是否比原来大,大就装上,不大就放弃

AC Code:

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

int main()
{
    int p, m;
    int i, j, n, x;
    while(cin>>n>>x)
    {
        int f[1001][1001] = {0};
        for(i = 1; i <= n; i++)
        {
            cin>>p>>m;
            for(j = 0; j <= x; j++)
            {
                f[i][j] = (i == 1? 0:f[i-1][j]);
                if(j >= p)
                {
                    f[i][j] = max(f[i-1][j],f[i-1][j-p]+m);
                }
            }
        }
        cout<<f[n][x]<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值