斐波那契数列的$O(\logN)$求法

欢迎来访

介绍求斐波那契数列时间复杂度为\(O(\log N)\)的做法之前,我们先看一下快速幂。

快速幂

题目链接

快速幂是数论中非常基础的算法。

当我们要求\(a^b mod p, (1 \le a, b, p \le 10^9)\)时,如果是朴素做法,时间复杂度为\(O(N)\)显然会超时,而快速幂能够做到的是将时间复杂度降到\(O(\log b)\)。

做法

首先预处理出:\(a^{2^0}, a^{2^1}, a^{2^2}, a^{2^3}, ..., , a^{2^{\log b}}\)

将每一项相乘,可以得到:\(a^{2^0+2^1+2^2+2^3+...+2^{\log b}}\)

我们知道:\(2^0+2^1+2^2+2^3+...+2^{\log b}\)可以转换成二进制表示:\(1111...111\)一共有\(\log b + 1\)个

利用\(2^i, 0 \le i \le \log b\)每一项选与不选可以凑出,\(0\) ~ \(2^{\log b + 1} - 1\)的任意整数。其中就包括我们要凑出的:\(b\)。

这一步的时间复杂度为\(O(\log b)\)。

C++代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

int qmi(int a, int b, int p) {
    LL res = 1 % p;
    while (b) {
        if (b & 1) res =  res * a % p;
        b >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

int main() {
    int n;
    int a, b, p;
    scanf("%d", &n);
    while (n--) {
        scanf("%d%d%d", &a, &b, &p);
        printf("%lld\n", qmi(a, b, p));
    }
    return 0;
}

斐波那契数列\(O(\log n)\)求法

首先先看一下斐波那契数列。

\[f(n) =
\begin{cases}
1, & \text{$n = 1$} \\\\[2ex]
1, & \text{$n = 2$} \\\\[2ex]
f(n-1)+f(n-2), & \text{$n \ge 2$}
\end{cases}
\]

我们设行向量\(F_n=[f_n, f_{n+1}]\),则:

\(F_1=[f_1, f_2]\)

\(F_2=[f_2, f_3]\)

我们看一下如何构造矩阵\(A\)使得\(F_1 \cdot A\)得到\(F_2\)

这个只要知道矩阵的乘法就不难构造出:

\(A=\begin{bmatrix} 0 & 1 \\ 1 & 1 \\ \end{bmatrix}\)

所以\(F_2=F1 \cdot A\),\(F_3=F2 \cdot A\),因为矩阵的乘法满足结合律,进而得到:

\(F_n=F_1\underbrace{A\cdot A\cdots A}_{\text{ n-1 times}}\),即\(F_n=F_1 \cdot A^{n-1}\)

这样我们就可以用快速幂来求了。

C++代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int MOD = 1e9 + 7;

LL res[2] = {1LL, 1LL};
LL A[2][2] = {
    {0LL, 1LL},
    {1LL, 1LL}
};

void mul(LL c[2], LL a[2], LL b[][2]) {

    LL tmp[2] = {0};
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 2; j++)
            tmp[i] = tmp[i] + (a[j] * b[j][i]) % MOD;

    memcpy(c, tmp, sizeof tmp);
}

void mul(LL c[][2], LL a[][2], LL b[][2]) {

    LL tmp[2][2] = {0};
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 2; j++)
            for (int k = 0; k < 2; k++)
                tmp[i][j] = tmp[i][j] + (a[i][k] * b[k][j]) % MOD;

    memcpy(c, tmp, sizeof tmp);
}

LL fib(int n) {

    n--;
    while (n) {
        if (n & 1) mul(res, res, A);
        n >>= 1;
        mul(A, A, A);
    }

    return res[0];
}

int main() {

    int n;
    scanf("%d", &n);

    printf("%lld", fib(n));

    return 0;
}

拓展:求斐波那契前 n 项和\(O(\log n)\)

题目链接

分析

与上面的思路相同,在行向量中再加上和\(S_n\)

我们设行向量\(F_n=[f_n, f_{n+1}, S_n]\),则:

\(F_1=[f_1, f_2, S_1]\)

\(F_2=[f_2, f_3, S_2]\)

构造矩阵\(A\)使得\(F_1 \cdot A=F_2\),不难发现:

\(A=\begin{bmatrix} 0 & 1 & 0 \\\\ 1 & 1 & 1 \\\\ 0 & 0 & 1 \\\\ \end{bmatrix}\)

C++代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

int n, m;

int res[3] = {1, 1, 1};
int A[3][3] = {
    {0, 1, 0},
    {1, 1, 1},
    {0, 0, 1}
};

void mul(int c[3], int a[3], int b[][3]) {

    int tmp[3] = {0};
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            tmp[i] = (tmp[i] + (LL)a[j] * b[j][i]) % m;

    memcpy(c, tmp, sizeof tmp);
}

void mul(int c[][3], int a[][3], int b[][3]) {

    int tmp[3][3] = {0};
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                tmp[i][j] = (tmp[i][j] + (LL)a[i][k] * b[k][j]) % m;

    memcpy(c, tmp, sizeof tmp);
}

int main() {

    scanf("%d%d", &n, &m);

    n--;
    while (n) {
        if (n & 1) mul(res, res, A);
        mul(A, A, A);
        n >>= 1;
    }

    printf("%d", res[2]);

    return 0;
}

参考

AcWing蓝桥杯

求解斐波那契数列的若干方法

原文地址:https://www.cnblogs.com/optimjie/p/12707530.html

时间: 2024-10-07 20:02:36

斐波那契数列的$O(\logN)$求法的相关文章

用递归和非递归的方法输出斐波那契数列的第n个元素(C语言实现)

费波那契数列(意大利语:Successione di Fibonacci),又译为费波拿契数.斐波那契数列.费氏数列.黄金分割数列. 在数学上,费波那契数列是以递归的方法来定义: {\displaystyle F_{0}=0} {\displaystyle F_{1}=1} {\displaystyle F_{n}=F_{n-1}+F_{n-2}}(n≧2) 用文字来说,就是费波那契数列由0和1开始,之后的费波那契系数就是由之前的两数相加而得出.首几个费波那契系数是: 0, 1, 1, 2, 3

Fibonacci斐波拉契数列----------动态规划DP

n==10 20 30 40 50 46 体验一下,感受一下,运行时间 #include <stdio.h>int fib(int n){ if (n<=1)     return 1; else            return fib(n-1)+fib(n-2); }int main( ){ int n; scanf("%d",&n); printf("%d\n" ,fib(n) );} 先 n==10 20 30 40 50 46

《剑指Offer》题目——斐波拉契数列

题目描述:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项.(n<=39) 题目分析:如果使用简单的递归,很容易造成栈溢出.采用递推的方式即可. 代码: public class Fibonacci { public static int fibonacci(int n){ int res[] = new int[2]; res[0]=1; res[1]=1; int temp = 0; if(n==0) return 0; if(n<=2) return res[

js算法集合(二) javascript实现斐波那契数列 (兔子数列) Javascript实现杨辉三角

js算法集合(二)  斐波那契数列.杨辉三角 ★ 上一次我跟大家分享一下做水仙花数的算法的思路,并对其扩展到自幂数的算法,这次,我们来对斐波那契数列和杨辉三角进行研究,来加深对Javascript的理解. 一.Javascript实现斐波那契数列 ①要用Javascript实现斐波那契数列,我们首先要了解什么是斐波那契数列:斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为

斐波那契数列

前几天学了javascript,挺难的比之前学的H5难多了,之前还觉得H5很难,一比较之下就相形见绌了. 在JS里面代码什么的还是蛮简单的,就是逻辑问题让你绕不过来....在这些逻辑问题里又有一个既难而且十分经典的问题,那就是斐波那契数列. 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为"兔子数列",指的是这样一个数列:1.1.2.3.5.8.13.21.34

斐波那契数列(分析别人的代码)

斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368........ 这个数列从第3项开始,每一项都等于前两项之和. n1 = 0 #给n1赋初始值 n2 = 1 #给n1赋初始值 count = 0 #给计数器初始值 while count < 10: #循环条件为计数器小于10 nth = n1 + n2 #n

快速求斐波那契数列(矩阵乘法+快速幂)

斐波那契数列 给你一个n:f(n)=f(n-1)+f(n-2) 请求出 f(f(n)),由于结果很大请 对答案 mod 10^9+7; 1<=n<=10^100; 用矩阵乘法+快速幂求斐波那契数列是经典应用: 矩阵公式 C i j=C i k *C k j; 根据递推式 构造2*2矩阵: 原始矩阵 1 0 0 1 矩阵 2 1 1 1 0 原始矩阵与矩阵 2相乘达到转化状态效果: 对矩阵二进行快速幂 乘法:达到快速转化矩阵的效果: 即使达到快速转化状态:那么大的数据范围也很难求解: 高精?这有

每日一九度之 题目1075:斐波那契数列

时间限制:5 秒 内存限制:32 兆 特殊判题:否 提交:3517 解决:2028 题目描述: 编写一个求斐波那契数列的递归函数,输入n值,使用该递归函数,输出如样例输出的斐波那契数列. 输入: 一个整型数n 输出: 题目可能有多组不同的测试数据,对于每组输入数据, 按题目的要求输出相应的斐波那契图形. 样例输入: 6 样例输出: 0 0 1 1 0 1 1 2 3 0 1 1 2 3 5 8 0 1 1 2 3 5 8 13 21 0 1 1 2 3 5 8 13 21 34 55 这题可以直

php实现斐波那契数列以及由此引起的联想

斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0.1.1.2.3.5.8.13.21.34.……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)在现代物理.准晶体结构.化学等领域,斐波纳契数列都有直接的应用.————摘自百度百科 公式: F(n)=F(n