动态规划算法原理:先解决子问题,再逐步解决大问题
01背包问题
假设你是个小偷,背着一个可以装4磅东西的背包,你可盗窃的商品有如下三件:
商品 | 价格 | 重量 |
音箱 | 3000美元 | 4磅 |
笔记本电脑 | 2000美元 | 3磅 |
吉他 | 1500美元 | 1磅 |
为了让盗窃的商品价值最高,你选择那些商品?
每个动态规划算法从一个网格开始,背包的网格如下
背包容量/商品 | 1 | 2 | 3 | 4 |
吉他 | ||||
音箱 | ||||
笔记本 |
(1)先看第一行,第一个单元格表示背包的容量为1磅,吉他的重量也是1磅,这意味着他能装入背包,因此这个单元格包含吉他,价值为1500美元,下一个单元格的背包容量为2磅,完全可以装下吉他。这行的其他单元格也一样,别忘了,这是第一行,只有吉他供你选择,假设你现在还没法盗取其他两间商品。
背包容量/商品 | 1 | 2 | 3 | 4 |
吉他 | 1500 | 1500 | 1500 | 1500 |
音箱 | ||||
笔记本 |
此时你有可能会很疑惑:原来的问题说的是4磅的背包,我们为什么要考虑容量为1磅,2磅等的背包呢?前面说过,动态规划从小问题着手,逐步解决大问题。
(2)我们来填充第二行,现在可以偷的商品有吉他和音箱。在每一行,可以偷得商品都为当前行的商品以及之前各行的商品。
该不该偷音箱呢?
背包容量为1磅,装不了音箱,所以最大价值依旧是1500美元,同理,2磅和3磅的背包也装不下音箱,因此最大价值保持不变。背包容量为4磅,可以装下音箱,此时最大价值就为3000美元了。
背包容量/商品 | 1 | 2 | 3 | 4 |
吉他 | 1500 | 1500 | 1500 | 1500 |
音箱 | 1500 | 1500 | 1500 | 3000 |
笔记本 |
(3) 下面以同样的方式处理第三行,笔记本电脑重3磅,没办法装入容量为1磅或2磅的背包,因此前两个单元格的最大价值还是1500美元。对于容量为3磅的背包,我们可以装笔记本电脑而不是吉他,此时的最大价值就可以更新为2000美元。
对于容量为4磅的背包,情况很有趣。
当前的最大价值为3000美元,你可以不偷音箱,而偷笔记本,但他只值2000美元,价格没有原来的高。但是,笔记本电脑的重量只有3磅,背包还剩下1磅没有用,此时我们想象,什么商品的重量刚好为1磅呢? 根据之前计算的最大值可以知道,1磅刚好可以装下吉他,价值1500美元。笔记本和吉他的总价值为3500美元,因此偷他们是最好的选择。
为什么计算小背包可装入商品的最大价值呢?
余下了空间时,可根据这些子问题的答案来确定余下的空间可以装入哪些商品。
背包容量/商品 | 1 | 2 | 3 | 4 |
吉他 | 1500 | 1500 | 1500 | 1500 |
音箱 | 1500 | 1500 | 1500 | 3000 |
笔记本 | 1500 | 1500 | 2000 | 3500 |
计算每一个单元格的价值时,我们可以使用如下公式:
bag[ i ] [ j ]=两者中较大的那个
1.上一个单元格的值(即 bag[ i-1 ] [ j ]的值) |
vs |
2.当前商品的价值 + 剩余空间的价值 ( bag[ i-1 ] [ j- 当前商品的重量 ] ) |
动态规划可以帮助你在给定的约束条件下找到最优解。在背包问题中,你必须在背包容量给定的情况下,偷到价值最高的商品。
在问题可以分解为彼此独立的且离散的子问题时,就可使用动态规划来解决。
最少硬币问题
题目描述
设有n 种不同面值的硬币,各硬币的面值存于数组T[1:n]中。现要用这些面值的硬币来找钱。可以使用的各种面值的硬币个数存于数组Coins[1:n]中。对任意钱数0≤m≤20001,设计一个用最少硬币找钱m的方法。
输入
输入的第一行中只有1 个整数给出n的值,第2 行起每行2 个数,分别是T[j]和Coins[j]。最后1 行是要找的钱数m。
输出
程序运行结束时,将计算出的最少硬币数输出。问题无解时输出-1。
样例输入
3 1 3 2 3 5 3 18
样例输出
5
提示
多组输入
思路:
动态迁移方程 a[ k ]=min( a[ k-t[ i ] ] + 1,a[ k ])
将第i个硬币拿出去得到的一个最少硬币数+1,和原硬币数相比最小的那个就是结果。
代码
#include<stdio.h>
int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
int n,m;
int i,j,k;
int t[100];
int coin[100];
while(~scanf("%d",&n)) //有几种不同的面值硬币
{
for(i=1;i<=n;i++)
scanf("%d%d",&t[i],&coin[i]);
scanf("%d",&m);
int a[200002]={0}; //用来记录钱数量为i时候的最少硬币数量
for(i=1;i<=m;i++)
a[i]=10000;
for(i=1;i<=n;i++) //面值硬币的种类数
for(j=1;j<=coin[i];j++) //面值硬币的个数
for(k=m;k>=t[i];k--)
{
a[k]=min(a[k-t[i]]+1,a[k]);
}
if(a[m]==10000) printf("-1\n");
else printf("%d\n",a[m]);
}
return 0;
}
先暂时写01背包问题,多重背包和完全背包问题我之后添加。