• 背包详解+模板


    背包详解请看    https://blog.csdn.net/Septembre_/article/details/81111812

    一:01背包

    特点:每种物品只有一件,可以选择放或不放

    dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);//状态转移方程

    这个方程非常重要,基本上 所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要详细讲解一下:”将前i件物品放入容量为j的背包中“这个子问题,若只考虑第i件物品的策略(放或者不放),那么就可以转化为一个只和前i-1件物品相关的问题。如果不放第i件物品,那么问题就可以转化为”前i-1件物品放入容量为j的背包中“,价值为dp[i-1][j];如果放入第i件物品,那么问题就可以转化为”前i-1件物品放入剩下的容量为j-wi的背包中“,此时能获得的最大价值就是dp[i-1][j-wi],再加上通过放入第i件物品获得的价值vi

    dp[i][j]为当前需要判断的物品,可以选择放或者不放,不放的话价值为dp[i][j-1] ,放的话价值为dp[i-1][j-wi]+vi,比较这两个的大小,选择大的价值。

    eg:给出n是物品数,c是背包的容量,v[i]价值 w[i]重量

    5 10
    6 3 5 4 6
    2 2 6 5 4
    

    将计算出的装入背包物品的最大价值和最优装入方案输出 15

    代码1

    #include <cstdio>
    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdlib>
    using namespace std;
    int main()
    {
        long long int n,c,v[999],w[999],dp[1000][1000],i,j;
        scanf("%lld%lld",&n,&c);
        for(i=1;i<=n;i++)
            scanf("%lld",&v[i]);
        for(i=1;i<=n;i++)
            scanf("%lld",&w[i]);
        for(i=1;i<=n;i++)//最好从1开始到n
        {
            for(j=1;j<=c;j++)
            {
                if(w[i]>j)
                    dp[i][j]=dp[i-1][j];
                else
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);//状态转移方程
            }
        }
        printf("%lld",dp[n][c]);
        return 0;
    }

    代码2(降低复杂度)

    #include<bits/stdc++.h>
    using namespace std;
    int dp[1005];//滚动数组的写法,省下空间省不去时间
    int weight[1005];
    int value[1005];
    int main()
    {
        int n,m;
        cin>>m>>n;
        memset(dp,0,sizeof(dp));
        for(int i=1; i<=n; i++)
            cin>>weight[i]>>value[i];
        for(int i=1; i<=n; i++)//对每个数判断,可反
        {
            for(int j=m; j>=weight[i]; j--)//这里这个循环定死,不能反,反了就是完全背包
            {
                dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);//其实不断在判断最优解,一层一层的
            }
        }
        cout<<dp[m]<<endl;
        return 0;
    }

    注意上面两个代码的区别 

    第一个dp是二维的,而第二个是一维的  第二个代码j循环要反着写

    如何理解二维降一维呢?对于外层循环中的每一个i值,其实都是不需要记录的,在第i次循环时,所有的dp[0…c]都还未更新时,dp[j]还记录着前i-1个物品在容量为j时的最大价值,这样就相当于还记录着dp[i-1][j]和dp[i-1][j-w[i]]+v[i]

    关键是内循环中为什么是逆序的呢,因为要记算当前状态的dp[j],需要的是前一状态的dp[j](即dp [j-1]),而逆序循环时前面的一定就是前一状态的dp[j],可以直接拿来用,而正序循环之所以不可以,是因为当你计算完前面的dp[j]时,dp[j-1]存的就不是i-1时刻的值了,而你后面还要用dp[j-1]。当内循环是逆序时,就可以保证后一个dp[j]和dp[j-w[i]]+v[i]是前一状态的!

    二:完全背包

    特点每种物品仅有无限件,可以选择放或不放

    memset(dp, 0, sizeof(dp));
    for(int i=0; i<n; i++){
        for(int j=w[i]; j<=c; j++){
        dp[j] = max(dp[j], dp[j-w[i]]+v[i])
        }
    }

    完全背包相比较于0-1背包,只需要将内循环由逆序遍历改为正序遍历就好了

    三:多重背包

    多重背包问题限定了一种物品的个数,解决多重背包问题,只需要把它转化为0-1背包问题即可。比如,有2件价值为5,重量为2的同一物品,我们就可以分为物品a和物品b,a和b的价值都为5,重量都为2,但我们把它们视作不同的物品。

    二进制分解思想:

    例如有22个重量和价值都为1的物品,如果按照常规方法的话,需要循环22次,每次放入一个重量质量都为1的物品,而用二进制分解的话,可以将22个物品转化为5个物品,5个物品的重量和价值为1,2,4,8,7;这样致需要循环5次就可以了。

    把22进行二进制拆分:

             成为1,2,4,8,7;由1,2,4,8可以组成1--15之间所有的数,而对于16--22之间的数,可以先减去剩余的7,那就是1--15之间的数可以用1,2,4,8表示了。

    下面为总的代码

    #include <stdio.h>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #define MAX 1000000
    using namespace std;
    int dp[MAX];//存储最后背包最大能存多少
    int value[MAX],weight[MAX],number[MAX];//分别存的是物品的价值,每一个的重量以及数量
    int bag;
    void ZeroOnePack(int weight,int value )//01背包
    {
        int i;
        for(i = bag; i>=weight; i--)
        {
            dp[i] = max(dp[i],dp[i-weight]+value);
        }
    }
    void CompletePack(int weight,int value)//完全背包
    {
        int i;
        for(i = weight; i<=bag; i++)
        {
            dp[i] = max(dp[i],dp[i-weight]+value);
        }
    }
    
    void MultiplePack(int weight,int value,int number)//多重背包
    {
        if(bag<=number*weight)//如果总容量比这个物品的容量要小,那么这个物品可以直到取完,相当于完全背包
        {
            CompletePack(weight,value);
            return ;
        }
        else//否则就将多重背包转化为01背包
        {
            int k = 1;
            while(k<=number)
            {
                ZeroOnePack(k*weight,k*value);
                number = number-k;
                k = 2*k;//这里采用二进制思想
            }
            ZeroOnePack(number*weight,number*value);
        }
    }
    int main()
    {
        int n;
        while(~scanf("%d%d",&bag,&n))
        {
            int i,sum=0;
            for(i = 0; i<n; i++)
            {
                scanf("%d",&number[i]);//输入数量
                scanf("%d",&value[i]);//输入价值  此题没有物品的重量,可以理解为体积和价值相等
            }
            memset(dp,0,sizeof(dp));
            for(i = 0; i<n; i++)
            {
                MultiplePack(value[i],value[i],number[i]);//调用多重背包,注意穿参的时候分别是重量,价值和数量
            }
            cout<<dp[bag]<<endl;//容量为bag的最优解
        }
        return 0;
    }
  • 相关阅读:
    每日分享!JavaScript中的表单事件
    每日分享!~ 如何解决获取卷曲高度的问题,document.body.scrollTop为什么在pc端拿不到值
    Unhandled rejection Error: EACCES: permission denied, open '
    每日分享!canvas的使用~
    每日分享!JavaScript的鼠标事件(11个事件)
    每日分享!~ 使用js原生方式对拖拉元素(鼠标的事件)
    每日分享!~ JavaScript(拖拽事件)
    每日分享!~ JavaScript(js数组如何在指定的位置插入一个元素)
    每日分享!~ vue JavaScript中为什么可以读取到字符串的长度!(包装对象)
    let和var以及const有什么区别
  • 原文地址:https://www.cnblogs.com/zcy19990813/p/9702749.html
Copyright © 2020-2023  润新知