dp入门与两个基础的背包问题

来源:互联网 发布:网络有重名怎么恢复 编辑:程序博客网 时间:2024/06/11 18:57

引子:

第一个问题:现在有3种硬币,1角钱,2角钱,3角钱,那么问题来了凑出n角钱最少需要多少硬币。

这个问题可以用贪心法解决。如果n比3大,那么一直取3就好了。最后少的要么是2,要么是1,所以答案是n/3+(n%3)?1:0;

第二个问题:现在有3种硬币,2角钱,3角钱,5角钱,那么问题来了凑出n角钱最少需要多少硬币。

如果n为16的话,一直取5那么最后剩1,就就不行了。

这时候就要用到动态规划。



01背包

01背包问题描述:现在有n个物品,人现在有一个容量为m的背包,每个物品有价值vi和重量wi,那么问题来了,在总重量不超过m的情况下使得价值最大,请问该怎么选?

这是最基础的问题,特点是对于每一种物体仅有一个,只存在放或者不放两种情况。

我们设p[i][j]表示前i个物体放入一个容量为j的背包时的最大价值。那么对于p[i-1]来说,我们可以看出就是一个取第i个和不取第i个的区别。那么就可以列出式子

p[i][j]=max( p[i-1][j] , p[i-1][j-w[i]]+v[i] );

所以我们可以列出式子

for(int i=0;i<n;i++)

for(int j=m;j>=w[i];j--)

p[j]=max(p[i-1][j],p[i-1][j-w[i]]+v[i]);

至于为什么第二重循环要从m循环到w[i]呢,其实在这里从m到w[i]或者从w[i]到m是无所谓的。

但是,如果只用一维数组来存结果,那么第二重循环就必须从m减到w[i]了。因为一维数组默认用到的前一个p[i-1][j]的值,我们必须保证用到的是上一层循环留下来的数据而不是这个i值留下来的新的数据。

for(int i=0;i<n;i++)

for(int j=m;j>=w[i];j--)

p[j]=max([j],p[j-w[i]]+v[i]);

是不是简单多了?




完全背包

完全背包问题是另外一种背包问题,还是原来的m,w[i],v[i],但是这时候每种可以取无数多个。

那么最直接的想法,f[i-1][j]=max{ f[i-1][v-k*v[i]]+k*w[i] },0<k<m/w[i]

然而这个算法复杂度是O(n^3)的,太恐怖了,简直不堪入目,不到万不得已不能用的。

那么怎么办呢?对于f[i-1][j]来说,再用取或者不取的思路来说,应该是f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i])

这时候我们可以想一想,上面01背包的第二重循环为什么要从m到w[i],就是为了保证每种只取一次。现在既然每种可以取无限次,那么就没必要从m开始循环了,从0开始恰好就满足了完全背包的条件。

for(int i=0;i<n;i++)

for(int j=v[i];j<=v;j++)

dp[j]=max(dp[j-1],dp[j-v[i]]+w[i]);

那么dp[n][m]就是答案了。