题目链接:AcWing 2. 01背包问题 https://www.acwing.com/problem/content/2/
递归前准备:递归函数参数设置
在尽量少的参数(节约栈空间)下保证不出现一组参数对应多个状态(参数不够)的情况。例如在本题中,需要设置2个递归参数:当前物品下标,背包剩余容量。假如缺少“当前物品下标”,那么无法保证一个物品不被重复取用,造成错误状态;假如缺少“背包剩余容量”,则无法判断一个物品是否可以取用。
递归主体三部分:终止条件,路径选择,返回值
终止条件
在递归函数体的最前面部分。当前状态处于合法状态的边界条件附近时(已经或者快要超出范围),继续递归拓展状态已经没有意义,此时给出返回值。
本题中,终止条件是:物品下标越界或者背包剩余容积小于等于0。此时返回“获得金钱”0。
if(index == N || vol_l <= 0)
return 0;
路径选择
位于函数体的中间部分。在当前状态合法时,满足状态转移前提条件的情况下可以继续递归到下一个状态。如果当前可选择状态较多,可以考虑用循环进行递归,但是递归太多次容易造成超时和栈溢出的情况。
本题中,对于任意物体,有两个决策:选(choose)或不选(refuse)。不管选不选,传给递归体的参数“物品下标”(index)都需要+1,因为01背包问题规定已经所有物品只能取用一次,无法进行二次决策。如果背包剩余容积允许,选择该物品导致容积减少,总价值增加。
refuse = dfs(index+1, vol_l);
if(vol_l >= goods[index].volume)
choose = dfs(index+1, vol_l-goods[index].volume)+goods[index].worth;
返回值
位于函数体末尾。通过主体的递归得到的数据经过处理整合成返回值。本题中,总价值取决于当前状态下选或者不选情况下总价值最大的一种。
maximum = max(refuse, choose);
return maximum;
完整代码:
#include <algorithm>
using namespace std;
struct item
{
int volume;
int worth;
};
int N, V;
item goods[1005];
int dfs(int index, int vol_l)
{
int choose, refuse = 0;
int maximum;
if(index == N || vol_l <= 0)
return 0;
refuse = dfs(index+1, vol_l);
if(vol_l >= goods[index].volume)
choose = dfs(index+1, vol_l-goods[index].volume)+goods[index].worth;
maximum = max(refuse, choose);
return maximum;
}
int main()
{
scanf("%d %d", &N, &V);
for(int i = 0; i < N; i++)
scanf("%d %d", &goods[i].volume, &goods[i].worth);
printf("%d", dfs(0, V));
return 0;
}
优化:通过状态数组记录返回值,进行记忆化搜索
优化原理:存在重叠子问题。背包问题作为经典的动态规划问题,后一个状态的返回值取决于前面的一个或者多个状态,因此会重复调用相同参数的dfs函数以获取前一个状态的返回值,造成重复计算。如果能在第一次算出某个状态时就将返回值存在状态数组,后面需要时取用,就能够避免重复计算,达到优化的目的。
#include <algorithm>
using namespace std;
struct item
{
int volume;
int worth;
};
int N, V;
item goods[1005];
int state[1010][1010];
int dfs(int index, int vol_l)
{
int choose = 0;
int refuse = 0;
if(state[index][vol_l] != -1)
return state[index][vol_l];
if(index == N || vol_l <= 0)
return 0;
refuse = dfs(index+1, vol_l);
if(vol_l >= goods[index].volume)
choose = dfs(index+1, vol_l-goods[index].volume)+goods[index].worth;
state[index][vol_l] = max(refuse, choose);
return state[index][vol_l];
}
int main()
{
for(int i = 0; i < 1010; i++)
for(int j = 0; j < 1010; j++)
state[i][j] = -1;
scanf("%d %d", &N, &V);
for(int i = 0; i < N; i++)
scanf("%d %d", &goods[i].volume, &goods[i].worth);
printf("%d", dfs(0, V));
return 0;
}