OJ算法–动态规划
算法概述
动态规划用于解决一定规模下的最优解问题,通过小规模最优解推导出大规模最优解,再由大规模最优解推导出更大规模最优解。以此类推最终得到问题答案,01背包问题就是最典型的动态规划问题。
例题
在刷牛客OJ时会遇到这样一道题:
算法原理
对于这样一个问题,影响问题规模的因素有两个,一个是需要凑出的邮票总价值,另一个是可用的邮票规模。当邮票总价值变小,或者可用邮票规模变小,亦或是两者同时变小时,问题都会变得更加简单。因此我们可以找出小规模问题和大规模问题中的某种递推公式,从而解决该题目。
因为影响问题规模的因素有两个,所以我们可以用一个二维数组来表示这个问题。如下图所示,j为邮票总价值,i为可用邮票,对于题目所给实例,可列画如下二维数组ans,此时ans[i][j]则表示在仅使用第i行及第i行以上邮票,凑出总价值为j的最少邮票数。不难看出除最后一行最后一列的ans外,任何一个ans[i][j]都是原问题的一个小规模版本,而最后一行最后一列的ans则是问题的最终答案。
这里第一列初始化为0是因为邮票总价值为0时,只需0张邮票就可满足条件。第一行除ans[0][0]外,初始化为99,是因为当无解时我们用99表示,这样在以后通过选取最小值的方式得到最优解的过程中,99可以被自动舍弃,这点后面会再提到。
之后我们要从左至右,从上至下依次计算出ans[i][j]。这也是从小问题向大问题不断靠近的过程。对于每一个ans[i][j]我们考虑这样一件事:此时的最优解无外乎两种情况,要么包含第i张邮票;要么不包含第i张邮票。如果不包含第i张邮票,也就是说仅使用前i-1张邮票即可得到总价值为j的最少拼凑方法,即ans[i][j] = ans[i-1][j];如果包含第i张邮票,则说明总价值j中包含第i张邮票的价值,那么抛去第i张邮票价值后的最优解再加上第i张邮票就是当前最优解,即ans[i][j] = ans[i - 1][j - val[i]] + 1。于是我们分别计算两种方法的ans[i][j]并取最小值即可。用此方法以此填满整个表格,最终最后一个值就是该问题的解。
可以看出,要求用1,3,3,3,4这5张邮票凑出10的面值,最少需要3张。
核心代码
#include <iostream>
#include <algorithm>
using namespace std;
int main(){
int value;
cin >> value;
int n;
cin >> n;
//记录邮票面值
int YouPiao[n];
for(int i = 0; i < n; ++i)
cin >> YouPiao[i];
//创建二维数组
int dp[n + 1][value + 1];
//初始化二维数组,未计算过的dp[i][j]赋值-1,方便调试
for(int i = 0; i < n + 1; ++i){
for(int j = 0; j < value + 1; ++j){
if(j == 0) dp[i][j] = 0;
else if(i == 0) dp[i][j] = 99;
else dp[i][j] = -1;
}
}
//算法核心:地推的方式计算整个二维数组的值
for(int i = 1; i < n + 1; ++i){
for(int j = 1; j < value + 1; ++j){
if(YouPiao[i - 1] <= j){
dp[i][j] = min( dp[i - 1][j] , dp[i - 1][j - YouPiao[ i - 1]] + 1 );
}
else{
dp[i][j] = dp[i - 1][j];
}
}
}
//如果最终不为99,则表示有最优解,否则无解
if(dp[n][value] != 99) cout << dp[n][value] << endl;
else cout << "0" << endl;
return 0;
}