01背包问题
下面给出 5 个物品信息和 1 个体积为 10 的背包:
number 1 2 3 4 5
volume 2 2 4 5 6
price 2 3 2 4 5
提出问题:找到这个背包能装下物品的最大价值是多少?
对于蒟蒻来说,这个问题很好解,直接模拟过程,遍历每个物品,选或不选,即0和1中做出选择,之后找到所有情况中的价值最大的那种搭配就行。
如果你是这样想的,那么我可以告诉你,你一定可以求出答案!
但是如果我把问题扩大一点:有1e6个物品信息,背包体积为capacity , 求最大价值。
那么你还能用同样的方法解出来吗?
所以这时候我们就要找到一个更加高效的算法。
下面分析问题: 从1e6个物品中找最大价值,我们最多要经过2的1e6次方的运算(天文数字),那么造成这么大的运算的直接原因是重复运算次数太多。(实际上是因为数据过大 )
举个例子,如果我要从1e6个物品中找出一个物品,使包内物体的价值最大,那么想都不要想,找 price 最高的。那么如果从1e6个物品中找 二 个物品,使包内价值最大怎么找呢?
蒟蒻的算法是这个样子的:如果第一个选 物品a,如果第二个物品选 b ,那么他们的总价值的就是V1, 记录V1的值,然后进行下一轮:如果第一个物品选的是a,第二个物品选的是c,那么他们的价值是V2,记录下来,然后重复上述步骤。这样下来,我们的运算次数最大是1e12。
那么我们现在优化一下:我们已经找到了包中装一个物品的最大价值,运算次数最大为1e6,那么我们现在只用找1e6个物品中第二大的物品,运算次数最大为1e6,那么这两个物品的价值和一定是最大的,且运算次数为2*1e6。和蒟蒻算法比价一下,不知道快了多少!
看完例子,相信大家已经知道什么是动态规划了吧!划重点:通过保存中间结果来避免不必要的重复计算,从而保证效率。
那么我们的优化代码怎么写呢?如下:
#include<bits/stdc++.h>
using namespace std;
int volume[100],price[100];
int v[100][100]={0};
int main()
{
int T,capacity;
cin>>T>>capacity;
for(int i=1;i<=T;i++)
{
cin>>volume[i]>>price[i];
}
for(int i=1;i<=T;i++)
{
for(int j=1;j<=capacity;j++)
{
if(v[i-1][j-volume[i]]+price[i]>v[i-1][j]&&j>=volume[i])
{
v[i][j]=v[i-1][j-volume[i]]+price[i];
}
else
{
v[i][j]=v[i-1][j];
}
}
}
cout<<v[T][capacity]<<endl;
return 0;
}
没看懂?待我细细道来:
我们先说v[n][m]的意义:其作用就是记忆过程,表示当剩余容积为m,从v个物体中放任意的物体时,包内的最大价值。
首先,我们必须明确:
1,在 0 个物体里选任意个物体放入背包,无论背包剩余容积为多少,其最大价值为 0 。(因为没东西了)
2,当背包的容积为0时,从 n 个物体中选任意个物体放入背包,包内最大价值为0(因为放不进去了)
所以v[0][n]和v[n][0](n表示任意值)都为0。
之后解释这段代码(核心代码):
if(v[i-1][j-volume[i]]+price[i]>v[i-1][j]&&j>=volume[i])
什么意思呢?这样想吧:现在我们走到了第 i 个物品前,我们要考虑一下到底要不要把这个物品放进包内。
打个比方:同样重量的砖头和金子,你会选哪个?(选砖头的自己拍死自己去吧! )为了使价值最大,肯定选的是金子。那么同样的,我选择放不放这个物品的依据就是:
我放了这个物品后的总重量是W,总价值是V1。放其他任意物品的总重量也是W,价值是V2。
那么如果V1>V2,就说明你很聪明,选了一个价值更高的东西。
所以就有这个判断:
v[i-1][j-volume[i]]+price[i]>v[i-1][j]
如果V1>V2,就跟新数据V[i][j],表示在这个状态下,包内的最大价值为多少。
如果V1<=V2,就将前面的数据代替到当前数据下。
所以通过两重循环,这个问题就解决了!