动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。这里先讲一下比较常见的三种背包问题。
01背包
首先先说一下dp算法最基础的问题,01背包问题。
假设有n件物品,每件物品的重量为weight[i],价值为value[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中
每种物品都只有一件。
这里我们用洛谷上的一道题作为例子,来描述这一类问题。
题目描述
P1048 [NOIP2005 普及组] 采药
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
第一行有 2 个整数 T(1≤T≤1000)和 M(1≤M≤100),用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。
接下来的 M 行每行包括两个在 1 到 100 之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
输入输出样例
输入
70 3
71 100
69 1
1 2
输出
3
#include <iostream>
#include <stdio.h>
using namespace std;
int w[105],val[105];
int dp[105][1005];
int main()
{
int t,m;
scanf("%d %d",&t,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&w[i],&val[i]);
}
for(int i=1;i<=m;i++)
{
dp[0][i]=0;
}
for(int i=1;i<=m;i++)
for(int j=t;j>=0;j--)
{
if(j>=w[i])
{
dp[i][j]=max(dp[i-1][j-w[i]]+val[i],dp[i-1][j]);
}
else
{
dp[i][j]=dp[i-1][j];
}
}
printf("%d",dp[m][t]);
return 0;
}
这里我们解决问题的方法就是设dp二维数组,dp[i][j],其中表示的是当前记录到第几个药了,j表示的是此时还剩多长时间,那么dp[i][j]表示的就是在考虑前i个药,而且还剩下时间j的情况下,能得的最大价值。
我们这里选择的是用w数组和val数组分别存储所用的时间和每一株草药的价值,假设我们现在筛选到了第i个,且剩余的时间是可以采这珠草药的,那么此时就会有两种情况,第一种是不采,那么此时dp[i][j]=dp[i-1][j];第二种是采,那么就是dp[i][j]=dp[i-1][j-w[i]+val[i];所以只要比较这两个的大小即可,如此递推,最后推出最后的结果。
状态转移方程的模板大致可以总结为
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
完全背包
关于完全背包,完全背包和01背包是类似的,不过不同点在于每个物品是无限多的,可以无限多的拿取,只要是背包的容量足够多。
模板是
dp[i][j]=max(dp[i-1][j],dp[i][j-weight[i]]+value[i];
那么这个是如何推导的呢,这里引用一下“曼切斯特的流氓”大佬的推导。
首先完全背包问题的动态转移方程可写为
(w为val[i]简写)(v=weight[i]简写)
dp(i,j)=max(dp(i-1,j) , dp(i-1,j-v)+w , dp(i-1,j-2v)+2w , dp(i-1,j-3v)+3w,
~~(以此类推到k) ,dp(i-1,j-k*v+kw))
我简单解释一下上面的方程,其实就是利用01背包思想,要求无限个物品,实际上我最多能装V/v[i]个 (总体积除以的单个个体积),所以我从装0个,到装一个,2个,3个,k个这里面一定有其中一个,是能产生最大的价值!
然后我们利用上述公式推导出"完全背包的状态转移方程"
开始推导
(时刻注意与这个方程的联系)
dp(i,j)=max(dp(i-1,j) , dp(i-1,j-v)+w , dp(i-1,j-2v)+2w , dp(i-1,j-3v)+3w,
~~(以此类推到k) ,dp(i-1,j-k*v+kw))
推导开始
还是利用01背包思想
dp(i,j-v)=max( dp(i-1,j-v) , dp(i-1,j-2v)+w,dp(i-1,j-3v)+2w , dp(i-1,j-4v)+3w,~~
~依次类推到k , dp(i-1,j-kv)+(k-1)w) )
我们在这个方程两侧同时加上w,即可得到
dp(i,j-v)+w=max( dp(i-1,j-v)+w , dp(i-1,j-2v)+2w,dp(i-1,j-3v)+3w , dp(i-1,j-4v)+4w,~~dp(i-1,j-kv)+kw)
我们在回顾一下这个方程
dp(i,j)=max(dp(i-1,j) , dp(i-1,j-v)+w , dp(i-1,j-2v)+2w , dp(i-1,j-3v)+3w,
~~(以此类推到k) dp(i-1,j-k*v)+kw))
可以发现dp(i,j-v)+w可以替代
dp(i-1,j-v)+w , dp(i-1,j-2v)+2w , dp(i-1,j-3v)+3w,~~, dp(i-1,j-k*v)+kw
之后就可以推出完全背包的公式了。
————————————————
这里贴上原文链接:https://blog.csdn.net/weixin_46503238/article/details/115096267
多重背包
这个问题也是01背包的变式,意思就是每个物品有有限值,但不一定只有1个,在这种情况下求最大价值。设s[i]为每种物体的最大个数。
这时候的状态转移方程为
dp[i][j] = max(dp[i-1][j-k*weight[i]]+k*value[i]),其中k<=s[i]且k*weight[i]<=j;
这里写了比较常见的三种背包问题,第一次写博客,同时也借鉴了一些大佬们的文章,希望大家多多包涵。