zoukankan      html  css  js  c++  java
  • 背包详解+模板

    背包详解请看    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;
    }
  • 相关阅读:
    Linq to OBJECT延时标准查询操作符
    LINQ to XML
    动态Linq(结合反射)
    HDU 1242 dFS 找目标最短路
    HDu1241 DFS搜索
    hdu 1224 最长路
    BOJ 2773 第K个与m互质的数
    ZOJ 2562 反素数
    2016 ccpc 杭州赛区的总结
    bfs UESTC 381 Knight and Rook
  • 原文地址:https://www.cnblogs.com/zcy19990813/p/9702749.html
Copyright © 2011-2022 走看看