文章目录
1 前言
1.1 什么是动态规划
动态规划(Dynamic Programming)是求解决策过程最优化的数学方法。把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。
1.2 什么时候要用动态规划
如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。
能采用动态规划求解的问题的一般要具有3个性质:
- 最优化原理
如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。 - 无后效性
即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。 - 有重叠子问题
即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
2 斐波那契数列 F i b o n a c c i Fibonacci Fibonacci
2.1 引入
有一头母牛,它每年年初生一头小母牛。每头小母牛从第二个年头开始,每年年初也生一头小母牛。请问在第n年的时候,共有多少头母牛?
2.2 定义
f i b ( n ) = f i b ( n − 1 ) + f i b ( n − 2 ) fib(n) = fib(n - 1) + fib(n - 2) fib(n)=fib(n−1)+fib(n−2)
0 0 0 1 1 1 1 1 1 2 2 2 3 3 3 5 5 5 8 8 8 13 13 13 21 21 21 34 34 34
2.3 递归分治解决 R e c u r s i o n Recursion Recursion
2.3.1 代码
int fib(n){
return (n < 2) ? n : fib(n - 1) + fib(n - 2);
}
int fib(n){
if(n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
2.3.2 时间复杂度分析
T ( n ) = T ( n − 1 ) + T ( n − 2 ) + 1 > 2 ⋅ T ( n − 2 ) + 1 = O ( ( 2 ) n ) T(n) = T(n - 1) + T(n - 2) + 1 \gt 2 \cdot T(n - 2) + 1 = O((\sqrt{2})^n) T(n)=T(n−1)+T(n−2)+1>2⋅T(n−2)+1=O((2)n)
T ( n ) = T ( n − 1 ) + T ( n − 2 ) + 1 = O ( f i b ( n ) ) = O ( Φ n ) T(n) = T(n - 1) + T(n - 2) + 1 = O(fib(n)) = O(\varPhi ^ n) T(n)=T(n−1)+T(n−2)+1=O(fib(n))=O(Φn)
Φ = 1 + 5 2 = 1.618... \varPhi = \frac{1 + \sqrt{5}}{2} = 1.618... Φ=21+5=1.618...
Φ 36 ≈ 2 25 \varPhi^{36} \approx 2^{25} Φ36≈225
Φ 5 ≈ 10 \varPhi^{5} \approx 10 Φ5≈10
2.4 解决方案
刚刚递归算法的不足之处在于重复计算了大量相同的子问题,浪费了大量时间
好记性不如烂笔头
记忆化搜索 M e m o i z a t i o n Memoization Memoization
备忘录 L o o k − u p Look-up Look−up T a b l e Table Table → \to → A r r a y Array Array
用一个数组记录需要重复计算的子问题
这就是动态规划的第一种实现方式,也是最容易理解的写法:记忆化搜索
2.5 记忆化搜索
const int maxn = 1e6 + 5;
int f[maxn];
void init(){
memset(f, -1, sizeof(f));
f[0] = 0;
f[1] = 1;
}
int fib(n){
int& ret = f[n]
if(ret != -1) return ret;
ret = fib(n - 1) + fib(n - 2);
return ret;
}
3 快速幂
3.1 问题描述
a 98765 = ? ? ? a^{98765} = ??? a98765=???
3.2 十进制快速幂
a 9 ⋅ 1 0 4 + 8 ⋅ 1 0