背包详解+模板
背包详解请看 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;
}