首先上来一句话,我个人感觉动态规划最重要的两点,就是【选择】和【结果】。
说白了就是因果,有什么因,那么对应的果是什么?
你做了什么样的选择,会有什么样的结果。
明白了这点再看这题。
背包问题,一堆物品,每样物品就一个,装哪些能让价值最大?
那么翻译一下这段话:面前一堆东西,各不重样,你【选择】拿什么放进你容量有限的包,从而得到【结果】,找出这群结果中价值最大的那一种【选择】。
那么【选择】是什么?
桌上的物品,这里可以把他们都看成是连续放在一起的,比如你可以想象,一张长条形的餐桌
摆放着一排连续的盘子,盘子上放着可供你选择的东西。
那么这里其实你就明白一点,我无论是从桌子这头开始选,还是从桌子那头开始选
都没差别。这是个组合的问题。
那么其实就无关紧要了,我选择从桌子这头开始选。
首先你的目光到了桌子最左边。当你还没选东西的时候。
你会不会想我如果【不选】呢?或者说我【选择】拿空气。
是不是我包无论多大,【结果】都是我包里的价值都是0。
那假设我拿起东西了呢?
什么变了?包的容量变了。
还有呢?我该看下一个东西了。因为前面的东西我都考虑过了我不需要再看了。
所以变得【状态】是我要去选择的物品编号和包的容量。
我们先从递归的方式开始考量。
一开始的我有什么?
我只有【从桌子左边】开始的这个初始点和【一个容量有限的包】
当我看向第一个物品,我有【选】和【不选】这两种选择。
同时还有个东西在限制我,我的包【容量】够不够让我去考虑这个当前的物品。
要是我包的容量根本放不下我眼前的物品了我又怎么需要花时间去考虑我到底要不要装它呢?
那么到这里,基本上代码都出来了。
#include <iostream>
#include <stack>
#include <vector>
#include <set>
int N;
int V;
int v[1050];
int w[1050];
int travel(int x,int vol);
int mem[1050][1050];
int main()
{
scanf("%d %d",&N,&V);
for(int i = 1;i<=N;i++)
{
scanf("%d %d",&v[i],&w[i]);
}
std::cout << travel(1,V) << std::endl;
}
int travel(int x,int vol)
{
if(mem[x][vol]) return mem[x][vol];
if(x>N)
{
mem[x][vol] = 0;
return mem[x][vol];
}
else if(vol<v[x]) return travel(x+1,vol);
else
{
mem[x][vol] = std::max(travel(x+1,vol),travel(x+1,vol-v[x])+w[x]);
return mem[x][vol];
}
}
那么递推呢?
回想一下我们构建这个记忆化数组的过程,我们是从1开始,递归到底,也就是考虑到N
然后自底向上归构建答案。
那么对于我们从桌子左边开始考虑的话,写递推就不得不从底也就是最右边的那个物品考虑
#include <iostream>
#include <stack>
#include <vector>
#include <set>
int N;
int V;
int v[1050];
int w[1050];
int travel(int x,int vol);
int mem[1050][1050];
int f[1050][1050];
int main()
{
scanf("%d %d",&N,&V);
for(int i = 1;i<=N;i++)
{
scanf("%d %d",&v[i],&w[i]);
}
for(int i = N;i>=1;i--)
{
for(int j = 0;j<=V;j++)
{
if(j<v[i]) f[i][j] = f[i+1][j];
else f[i][j] = std::max(f[i+1][j],f[i+1][j-v[i]]+w[i]);
}
}
std::cout << f[1][V] <<std::endl;
return 0;
}
说白了就是从记忆化搜索改成递推的时候,
从归的起点出发,写递推。
本质思想其实是,对于当前这个物品,考虑到我背包的容量
要是我背包的容量都不够装这个,我只能【放弃】【选择】!
那么放弃选择的结果是什么?
就是我的包还是原来的状态!即容量不变的情况下考虑上一个物品的情况。
以此类推。