背包详解+模板

背包详解请看    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;
}
原文地址:https://www.cnblogs.com/zcy19990813/p/9702749.html