引子:
第一个问题:现在有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]就是答案了。