介绍求斐波那契数列时间复杂度为\(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;
}
参考
原文地址:https://www.cnblogs.com/optimjie/p/12707530.html