水里铁氰化亚铁水里的铁氰化亚铁

【动态规划1】01背包,完全背包与多重背包

Toretto·2022-09-20 15:14·105 次阅读

【动态规划1】01背包,完全背包与多重背包

1. 01背包

01背包就是指背包里面每种物品只能用一次,在有限的空间内求装入物品的最大价值的问题。

1. 题面:#

有一容量为m的背包。现在要从n件物品中选取若干装入背包中,每件物品i的重量为w[i],价值为p[i],定义一种可行的背包装载为:背包中物品的总重量不能超过背包的容量,并且一个物品要么全部选取,要么不选取。定义最佳装载是指所装入的物品价值最高,并且是可行的背包装载。

Copy
输入输出: 样例输入 样例输出 11 (背包容量) 23(总价值) 4(物品数量) 2 4 6 7(物品重量) 6 10 12 13(物品价值)

2. 递归解法#

考虑到这个题目,用贪心算法显然是鼠目寸光的。所以思考其他思路。自顶向下地看这个题目,对于第n件物品而言,无非有两个状态(选择):取或者不取。如果取,在获得这件物品价值地同时,也要承担其重量,如果不取,就无需承担,当然也得不到它的价值。满足无后效性。那么,这种问题就可以递归地求解了。

Copy
//返回第n件物品的最大价值,weight为背包剩余容量 int dfs(int n,int weight) { if(n==0||weight==0) return 0;//递归终止条件 int a = dfs(n-1,weight]);//不取,直接返回n-1的状态,不消耗weight int b = dfs(n-1,weight-w[n])+v[n];//取,消耗掉w[n]空间的同时获得了v[n]的价值 return a>b?a:b;//返回最大值 }

3. 优化递归:记忆化搜索#

对于上述递归解法,每求一次dfs(n)就要用到很多次dfs(2),dfs(3)等,而这些必须用一次算一次,增加了时间复杂度。所以,我们考虑使用一个数组把他们存起来,即记忆化搜索,又叫备忘录方法,这样就可以减小时间复杂度。

Copy
int dp[100005]; int dfs(int n,int weight) { if(n==0||weight==0) return 0;//递归终止条件 if(dp[n]!=-1) return dp[n]; int a = dfs(n-1,weight]);//不取,直接返回n-1的状态,不消耗weight int b = dfs(n-1,weight-w[n])+v[n];//取,消耗掉w[n]空间的同时获得了v[n]的价值 if(a>b) { dp[n] = a; }else dp[n] = b; return dp[n];//返回最大值 }

4.动态规划#

对于这类无后效性,备忘录的问题,使用动态规划显然是最佳解法。对于这个题,我们使用f[i][j]作为取第i件物品时容量为j的最大利润,即可推导出状态转移方程:
f[i][j] = max(f[i-1][j],f[i-1][j-w[i]]+v[i])
由此即可写出基本代码:

Copy
for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (w[i] > j) { f[i][j] = f[i - 1][j]; } else { f[i][j] = f[i - 1][j] > f[i - 1][j - w[i]] + v[i] ? f[i - 1][j] : f[i - 1][j - w[i]] + v[i]; } } }

5.滚动数组#

在这个过程中,我们发现所要的仅仅是f[n][m],即n个物品容量为最大容量m时的情况,而前面n-1的数组迭代都是无意义的。因此,可以用一维数组代替二维数组,进一步优化空间复杂度。此时的状态转移方程如下:
f[j] = max(f[j],f[j-w[i]]+v[i])
此时得到的代码:

Copy
for (int i = 1; i <= n; i++) { for (int j = m; j >=w[i]; j--) { f[j] = max(f[j],f[j-w[i]]+v[i]); } }

这里代码中的第二行使用了由后向前的遍历方式,而完全背包问题则恰好相反,是由前到后的遍历方式。举个例子,若 j=4:
f[4] = max(f[4],f[4-w[i]]+v[i]);
因为是由后到前,我们再看遍历到j=2时的情况:
f[2] = max(f[2],f[2-w[i])+v[i]);
若w[i]=2,初始化之后,因为f被置为0,先算w[4]时w[2]是为0的,这样就避免了f[2]被多次相加的问题。反之,若是完全背包问题,先计算f[2],f[2]的值已经不为0,此时计算f[4] = max(f[4],f[2]+v[i]);这样j每翻一倍,f[4]就也会翻一倍,符合完全背包问题的要求。

最简单的例子如洛谷采药,疯狂的采药,又如蓝桥杯2018年的分包子问题,2021年的天平问题等。

posted @   PARADOXS  阅读(105)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示
目录