• 【背包专题】01背包


    暑假集训开始了,按照队里的分配,我是弄DP的,嘛,于是我又一次的开始了从01背包开始学习,昨天将杭电的几道01背包重新做了一遍,下面讲讲我自己对于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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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 main()
    {
        char ch;
        double sum,a,b,c,dp[35],money[35],v;
        int t,i,j,k;
        while(~scanf("%lf%d",&sum,&t),t)
        {
            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,&v);
                    if(ch == 'A' && v<=600)
                        a+=v;
                    else if(ch == 'B' && v<=600)
                        b+=v;
                    else if(ch == 'C' && v<=600)
                        c+=v;
                    else
                        flag = 0;
                }
                if(a+b+c<=1000 && a<=600 && b<=600 && c<=600 & flag)//按题意所说,必须满足这些条件
                    money[l++] = a+b+c;
            }
            for(i = 0; i<=l; i++)
            {
                for(j = l; j>=1; j--)
                {
                    if(j == 1 || dp[j-1]>=0 && dp[j-1]+money[i]<=sum)//状态转移
                        dp[j] = max(dp[j],dp[j-1]+money[i]);
                }
            }
            int ans = 0;
            for(i = 1; i<=l; i++)
            {
                if(dp[ans]<dp[i])//找出最大
                    ans = i;
            }
            printf("%.2lf
    ",dp[ans]);
        }
    
        return 0;
    }
    


     

    恩,01背包的专题就到这里了,第一次说算法,说得不咋样,语言表达能力有限,各位看客求宽容啊!!!

  • 相关阅读:
    在web项目下注册MySQL数据库驱动失败
    Servlet 调用过程
    请求时参数到后台解码时会出现乱码问题
    Request 部分功能
    dom4j增删改查
    微信消息处理JAXP-sax解析
    微信消息处理JAXP-dom解析
    inputstream与其他格式的转换
    微信消息处理
    将Gridview导出到Excel
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3193972.html
Copyright © 2020-2023  润新知