DP
这是我第一天学习动态规划的一点笔记和小小心得吧。
**概念:**递归+记忆化
标志:
1、计数问题:有多少种方式?
2、求最值:路径和问题、最值问题
3、存在性:是否必胜?选k个数求sum值
动态规划三要素:
1.最优子结构
2.边界
3.状态转移方程
例1、有2、5、7三面值硬币,凑27总数,求最少组合方案(最值问题)
**step 1:**确定状态
-
终点
-
子问题
终点分析:总数27,设硬币数为k,终点硬币面值为ak,27-ak对应k-1枚硬币面值。
子问题:最少用多少硬币凑27-Ak?
目的:缩小问题规模
设状态方程f(x)=最少硬币数
终点可能为Ak = 2,5,7
所以,f(27)= f(27-2||5||7)+1
f(27)= min{f(27-2)+1,f(27-5)+1,f(27-7)+1}
递归思路:
int f(int x){ if(!x)return 0; int res = INT_MAX; if(x>=2){ res=min(res, f(x-2)+1); } if(x>=5){ res=min(res, f(x-5)+1); } if(x>=7){ res=min(res, f(x-7)+1); } return res; }
存在问题:
递归存在大量重复增加了算法的时间复杂度
改进思路:空间换时间,即用数组保存已经算过的值。
**step 2:**确定转移方程
f[x]= min{f[x-2]+1,f[x-5]+1,f[x-7]+1}([]表示一般以数组形式代替函数)
**step 3:**确定初始条件和边界
问题:例如<0情况和停止条件
1、拼不出
f[<0] = +无穷
例如:f[1] = min{f[-1]+1,f[x-4]+1,f[x-6]+1} = +无穷,表示无法凑1
2、初始化
f[0] = 0;
**step 4:**确定计算顺序
根据转移方程和初始条件确定顺序。
此题,转移方程为:f[x]= min{f[x-2]+1,f[x-5]+1,f[x-7]+1},初值为f[0] = 0;
因此顺序为从小到大。
例如:f[10] = min{f[8]+1,f[6]+1,f[2]+1},先计算f[8],f[6],f[2]保存即可快速得到f[10]
最终代码:
#include <iostream>
#include <climits>
#include <cstdlib>
#include <cstring>
using namespace std;
int a[100];
int f(int x){
if(x < 0)return 1e9;
int x1 = x - 2;
int x2 = x - 5;
int x3 = x - 7;
int min_value;
if(x2 >= 0)
{
min_value = min(a[x - 2]+1, a[x - 5]+1);
}
else if(x1 >= 0)
{
min_value = a[x - 2]+1;
}
else
{
min_value = 1e9;
}
if(x >= 0){
if(x3 >= 0){
min_value = min(min_value, a[x - 7]+1);
}
else{
min_value = min_value;
}
}
return min_value;
}
int main() {
memset(a, 1e9 ,sizeof(a));
a[0] = 0;
int i, n;
cin>>n;
for(i = 1;i <= n;i++)
{
a[i] = f(i);
}
cout<<a[n]<<endl;
return 0;
}