最近学习算法,刚刚接触动态规划,整了一下午终于整对一道题,发到博客供交流及复习。
先大致总结一下自己理解的动态规划。
动态规划的两个特点:
1,当前状态的最优解可以由上一个状态的最优解推出(子问题有最优解)
2,整个求解过程中许多状态会被多次求解(重叠子问题)
解决动态规划问题时,首先要自顶向下分析(求得递归式),再自底向上写代码(避免重复求解子问题)。
题目如下:
假设我们有8种不同面值的硬币{1,2,5,10,20,50,100,200},用这些硬币组合够成一个给定的数值n。例如n=200,那么一种可能的组合方式为 200 = 3 * 1 + 1*2 + 1*5 + 2*20 + 1 * 50 + 1 * 100. 问总过有多少种可能的组合方式?(原题及数学分析见http://www.cnblogs.com/python27/p/3303721.html)
自顶向下的分析见上述链接,我这里给出自底向上的分析。
设一个200*8的二维数组c[200][8],令c[i][j]的值表示:用前j个硬币组合出数值i的所有可能组合总数,如c[100][3]表示用面值为1、2和5的硬币组合成100的可能组合数,则可以得出如下矩阵:
上述矩阵每个元素c[i][j] = sum(c[k][j-1])
其中k=(i, i-1*Vj, i-2*Vj, ……,i mod Vj)。Vj表示子问题由由前j-1个硬币转移到前j个硬币时所添加的硬币面值(如由前2个硬币可用转移到前3个硬币可用时,添加的硬币面值为5,则V3=5)
例如c[6][3] = c[6][2]+c[1][2],其实从这个例子很容易理解其中的原理:c[6][3](用1、2、5三个硬币组合成6的组合数)等于两个子问题的和,一个子问题是c[6][2](用1、2组合成6的组合数,即使用任意数量的1、2而使用0个5),另一个子问题是c[1][2](用1、2组合成1的组合数,即使用任意数量的1、2同时使用1个5)
附代码:
#include <iostream>
using namespace std;
/*
void print(int **a, int xx, int yy) {
for (int x = 0; x < xx; x++) {
for (int y = 0; y < yy; y++) {
cout << a[x][y] << " ";
}
cout << endl;
}
}
*/
int GetCombines(int CoinValue[], int CoinNumber, int Goal) {
int **dp = new int*[Goal + 1];
int ret;
for (int i = 0; i <= Goal; i++) {
dp[i] = new int[CoinNumber];
}
for (int i = 0; i <= CoinNumber; i++) {
dp[0][i] = 1;
}
for (int j = 1; j <= CoinNumber; j++) {
for (int i = 1; i <= Goal; i++) {
for (int k = i; k >= 0; k = k - CoinValue[j - 1]) {
dp[i][j] += dp[k][j - 1];
}
}
}
// print(dp, Goal, CoinNumber);
ret = dp[Goal][CoinNumber];
for (int i = 0; i <= Goal; i++) {
delete dp[i];
}
delete dp;
return ret;
}
int main() {
int CoinValue[] = { 1, 2, 5, 10, 20, 50, 100, 200 };
int CoinNumber = 8;
int Goal = 200;
cout << GetCombines(CoinValue, CoinNumber, Goal) << endl;
}