• 基础DP(2)


    0/1背包

    如果每个物体可以切分,称为一般背包问题,用贪心法求最优解,从最大的最多的开始。

    如果每个物体不可分割,称为0/1背包问题。0和1代表装入背包和不装入背包。

    举个栗子:有4个物品,其重量分别是2、3、6、5,价值分别为6、3、5、4,背包的容量为9。

    先定一个二维数组dp[i][j],i代表物品个数,j代表当前容量,dp[i][j]代表当前价值。

     可以参照一下硬币问题的图。

    从小问题扩展到大问题,先只装第一个物品,然后只装前两个物品,依次......直到装完。

    在只装第一个物品的情况下,第一个物品重量为2,那么容量小于2的放不进去,因此dp[1][0]和dp[1][1]都为0,dp[1][2]=6,容量大于2的和等于2的一样(dp[1][3]=6......)。

    只装前两个物品时,如果容量不能装第二个物品,就和只装第一个物品的情况相同,那么dp[i][j]=dp[i-1][j],也就是dp[2][3]=dp[1][3]=6;如果能装第二个物品,又分为两种情况,如果装第二个物品,那么dp[2][3]=3,如果不装第二个物品,就和不能装的情况相同,dp[2][3]=6,因为要总价值最大,所以取两种情况的最大值,也就是max{dp[i-1][j],dp[i-1][j-v(i)]+value(i)},这里的dp[i-1][j]是不装第i个物品时的情况,dp[i-1][j-v(i)]+value(i)是装了物品i的情况,v(i)是i的容量,value(i)是i的价值,因为装了物品i,所以当前容量为j-v(i)(聪明的你肯定一看就懂,不懂就再想想)。

    来,放题:

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

    思路和上面一样。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    
    int t,n,c;
    int dp[1005][1005],v[1005],value[1005];
    
    int main()
    {
        scanf("%d",&t);
        while(t--){
            memset(dp,0,sizeof(dp));
            scanf("%d%d",&n,&c);//n是骨头数量,c是背包体积
            for(int j=0;j<n;j++){
                scanf("%d",&value[j]);//价值
            }
            for(int k=0;k<n;k++){
                scanf("%d",&v[k]);//体积
            }
            for(int x=1;x<=n;x++){
                for(int y=0;y<=c;y++){
                    if(v[x]>y){//容量不能装第x个物品时
                        dp[x][y]=dp[x-1][y];//价值和上一个一样
                    }
                    else
                    dp[x][y]=max(dp[x-1][y],dp[x-1][y-v[x]]+value[x]);//能装时,求两种情况较大值以得最大价值
                }
            }
            printf("%d
    ",dp[n][c]);
        }
        return 0;
     } 

    最后有一个滚动数组的技巧,处理dp[][]状态数组时把它变成一维的dp[]以节省空间,因为第x行由x-1行推出,所以可以直接覆盖上一行。

    滚动数组代码:

    int dp[1005];//原来是int dp[1005][1005] 
    for(int x=1;x<=n;x++){//其余部分省略了,和上面一样 
        for(int y=c;y>=v[x];y--){//当然你也可以边读边处理
            dp[y]=max(dp[y],dp[y-v[x]]+value[x]);
        }
    }

    因为会遇到较大的数据量,所以为了节省空间使用滚动数组(空间复杂度从O(NV)减少为O(V)),但是时间复杂度上没有优化,并且因为它覆盖了中间转移状态,只留下了最后的状态,导致无法输出背包的具体方案。

    更新:

    01背包变形:https://www.luogu.com.cn/problem/P1164

     思路:因为每一个菜要么吃要么不吃,所以当钱足够买这个菜的时候方法数为吃这个菜的方法数+不吃这个菜的方法数,当钱不够时就没法买,方法数为上一道菜的方法数。设dp[][]为方法总数,那么当目前拥有的钱j小于这一道菜价格a[i]时,dp[i][j]=dp[i-1][j];注意当j等于这个菜价格的时候,可以只买这一道菜,dp[i][0]就等于1,或者可以判断当j==a[i]时,dp[i][j]=dp[i-1][j]+1;当j大于a[i]时,也就是说前i个物品中所有能凑出j-a[i]元的方案再加上当前这道菜品,dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]]。

    代码:

    #include<iostream>
    #include<algorithm>
    #include<stdio.h>
    using namespace std;
    
    int a[105];
    int dp[105][10005];
    int main()
    {
        int n,m;
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(j<a[i]) //钱不够买这道菜时,方法数为前一道菜的方法数
                dp[i][j]=dp[i-1][j];
                else if(j==a[i]) //注意特别判断刚好能买这一道菜的时候,dp[i][0]=1
                dp[i][j]=dp[i-1][j]+1; 
                else //钱够的时候,方法数为买这道菜和不买的方法数的和
                dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]];
            }
        }
        printf("%d
    ",dp[n][m]);
        return 0;
    }

    https://www.luogu.com.cn/problem/P1510

    笑死,做的时候感觉思路没问题,就一道模板题,结果wa了,回头一看让输出剩下的最大体力。

    0/1背包:

    https://www.luogu.com.cn/problem/P1048

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

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

    滚动数组:

    https://www.luogu.com.cn/problem/P2871

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

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

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

    EOF

  • 相关阅读:
    js学习总结----多级菜单jquery版本
    js学习总结----案例之多级菜单js版本
    js学习总结----案例之百度搜索框
    js学习总结----案例之放大镜
    js学习总结----事件委托和事件代理(鼠标点击其他地方隐藏效果)
    js学习总结----鼠标跟随js版
    js学习总结----案例之鼠标跟随jquery版
    js学习总结----事件的传播机制
    js学习总结----事件基础
    js学习总结----jquery版本轮播图及extend扩展
  • 原文地址:https://www.cnblogs.com/Untergehen/p/14322555.html
Copyright © 2020-2023  润新知