暴力递归
int f(int n){
return (n<=2) ? 1 : f(n-1)+f(n-2);
}
这样画出递归树会发现同一个n值,f(n)被算了很多遍。比如f(5)的递归树:
越底层重复算的次数越多,时间用这个树的节点个数来算,就是指数级别的可怕结果。
记忆化搜索
可以开个数组把算过的记下来,再算就直接用不用往下递归。
int f(int n){
return F[n] ? F[n] : (F[n]=f(n-1)+f(n-2));
}
上面的树会变成
第二次f(3)的子树没了,记忆化搜索是动态规划的雏形。
数组递推
for(int i=1;i<=n;i++){
F[n]=F[n-1]+F[n-2];
}
直接f[n]=f[n-1]+f[n-2]。O(n)循环一遍。
滚动递推
Fn_1=1,Fn_2=1;
for(int i=3;i<=n;i++){
Fn=Fn_1+Fn_2;
Fn_2=Fn_1;
Fn_1=Fn;
}
我们发现n只和n-1和n-2有关,所以只要滚动维护当前的三个数就行。
矩阵快速幂
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
∗
[
f
(
n
−
1
)
f
(
n
−
2
)
]
\begin{bmatrix} f(n)\\f(n-1)\end{bmatrix}=\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}*\begin{bmatrix} f(n-1)\\f(n-2)\end{bmatrix}
[f(n)f(n−1)]=[1110]∗[f(n−1)f(n−2)]
根据矩阵的结合律可以用二进制快速幂求解。时间复杂度O(logn)。
完整代码:
#include<cstdio>
using namespace std;
class mtrx22{
public:
int a[2][2];
friend mtrx22 operator *(mtrx22,mtrx22);
mtrx22(int aa,int b,int c,int d){
a[0][0]=aa,a[0][1]=b,a[1][0]=c,a[1][1]=d;
}
void print(){
printf("%d %d\n",a[0][0],a[0][1]);
printf("%d %d\n",a[1][0],a[1][1]);
printf("\n");
}
};
mtrx22 operator *(mtrx22 x,mtrx22 y){
mtrx22 neww(0,0,0,0);
neww.a[0][0]=x.a[0][0]*y.a[0][0]+x.a[0][1]*y.a[1][0];
neww.a[0][1]=x.a[0][0]*y.a[0][1]+x.a[0][1]*y.a[1][1];
neww.a[1][0]=x.a[1][0]*y.a[0][0]+x.a[1][1]*y.a[1][0];
neww.a[1][1]=x.a[1][0]*y.a[0][1]+x.a[1][1]*y.a[1][1];
return neww;
}
mtrx22 quick_pow(mtrx22 d,int x){
mtrx22 p=d,res(1,0,0,1);
while(x){
if(x%2==1){
res=p*res;
}
x/=2;
p=p*p;
}
return res;
}
int main(){
int n;scanf("%d",&n);
mtrx22 loww(1,1,1,0);
mtrx22 ans=quick_pow(loww,n-2);
printf("%d\n",ans.a[0][0]+ans.a[0][1]);
return 0;
}
求特征矩阵带出公式
这个是就出底数矩阵的特征矩阵,然后利用对角阵的优良性质带出通向公式。但是特征值是无理数,所以还要求无理数的高次运算,复杂度还是log的,而且容易产生精度问题。所以还不如用矩阵快速幂做。