求斐波拉切数列常用方法:
递归法:O(n)=n*n
for循环法:O(n)=n
矩阵快速幂法:O(n)=logn
先介绍数快速幂法
求一个数的n次方,如果n很大,比如:
int n;
int a;
cin>>a;
cin>>n;
pow(a,n);
//n>100000000
这时如果再用头文件#include<cmath>中的pow函数会非常慢,所以不可取。
如果用for循环方法,比如2^11,用for循环要计算11次,但是数字11可以化为二进制1011,所以也等于2^(1011),同时,(1011)等于2^0+2^1+2^3,那么2^11也等于2^(2^0+2^1+2^3),将式子拆成乘法形式:2^11=2^(2的0次方)+2^(2的一次方)+2^(2的3次方),可以发现,这三个数字后一个都是前一个的平方。这就是代码中的:a=a*a;(代码在下文)
同时,如果要我们计算3^10,我们该如何计算,我们首先想到的流程就是这样:
3^10---->9^5---->9*9^4---->9乘以(81)^2---->9乘以(81^2)^1,
其实这也利用了快速幂的方法:
先定义一个变量ans来存储最终结果
如果指数是偶数,将底数平方、再将指数除以二。
如果指数是奇数,先将ans*底数,然后指数就减少了1,就可以按照上一步的偶数进行操作了,直到指数为0
具体代码如下:
#include<iostream>
using namespace std;
typedef long long ll;//因为数字很大,要用long long存储
ll pow(ll a,ll n)//a是底数,n是指数
{
ll res; //存储最后的结果
while(n)//指数为0停止循环
{
if(n&1)//这个是什么意思,文章最后会说
res=res*a;
a=a*a;
n=n>>1;//二进制中这就相当于除以2
}
retur res;
}
再介绍矩阵快速幂法
基础知识:矩阵的乘法运算。此处不再多言。
注意:矩阵乘以数字叫做矩阵的数乘,矩阵乘以矩阵才叫矩阵的乘法运算
在斐波那契数列之中
f[i] = 1f[i-1]+1f[i-2] f[i-1] = 1f[i-1] + 0f[i-2];
即
所以
就这两幅图完美诠释了斐波那契数列如何用矩阵来实现。
与数快速幂法的区别就是用矩阵代替数字进行操作运算,实际核心思想没有任何变化。具体代码如下:
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
typedef vector<ll> vec;
typedef vector<vec> mat;//这里是vector容器嵌套,数组嵌套数组,用作二维数组。mat是矩阵常用词语
//矩阵相乘算法(需要重载运算符,因为*起初的定义是数字与数字之间的,现在我们要用于矩阵与矩阵)
mat operator * (mat a,mat b)
{
mat c(2,vec(2));//定义两行两列数组,因为用到的数组是已经确定的,所以可以直接定义两行两列数组
for(int i=0;i<2;i++)
for(int k=0;k<2;k++)
for(int j=0;j<2;j++)
c[i][j]+=a[i][k]+b[k][j];
return c;
}
//矩阵快速幂算法
mat pw(mat a, ll n)
{
mat c(a.size(),vec(a.size()));
for(int i=0;i<a.size();i++)
{
c[i][i]=1;//初始化矩阵,相当于在数字中给一个变量初始化为1一样
}
while(n)
{
if(n)
c=c*a;
a=a*a;
n=n>>1;
}
return c;
}
//主函数进行初始准备
int main()
{
ll n;
cin>>n;
mat a(2,vec(2));//创建一个初始化数组作为累乘数组
a[0][0]=1;
a[0][1]=1;
a[1][0]=1;
a[1][1]=0;
a=pow(a,n);
cout<<a[0][1];
return 0;
}
n&1与1&n
n&1判断n的二进制表示末尾是不是1(判断n是不是奇数),是的话返回1,不是的话返回0
1&n检查n的二进制的最低为,如果是1就返回1,否则就返回0
注意事项
-
(n=n>>1)!=(n>>1)这两个式子不一样,n>>1的返回值是一个新的数字,要用原来的变量来接受才可以改变原先变量的值。
-
矩阵也要初始化成为单位矩阵,即对角线上(左上角到右下角)的数字全部为1,其他都是0,不初始化的话,最终结果会一直是0.