我刚刚解决了a problem in Codechef,需要在O(log n)中找到第N个Fibonacci数。我使用了here提到的快速加倍方法:
F(2N) = F(N) * ( 2F(N+1) - F(N) )
F(2N+1) = F(N+1)^2 + F(N)^2
以下是我的代码示例。迭代版本在上面给出的链接中的代码示例中给出:
快速加倍递归
#include <map>
#include <iostream>
using namespace std;
#define long long long
const long M = 1000000007; // modulo
map<long, long> m;
long F(long n)
{
if(m.count(n))return m[n];
long a, b;
if((n&1) == 0)
{
a = F(n/2)%M;
b = F((n/2) + 1)%M;
return m[n] = (((2*a*b) % M - (a*a)%M) + M) % M;
}
else
{
a = F((n+1)/2)%M;
b = F((n-1)/2)%M;
return m[n] = ((a*a) % M + (b*b)% M) % M;
}
}
int main()
{
m[0] = 0;
m[1] = m[2] = 1;
printf("%lld", F(100000000));
}
快速加倍迭代
#include <iostream>
using namespace std;
#define long long long
const long MOD = 1000000007;
long F(long n)
{
long a = 0, b = 1, d, e, c;
int i = 31;
while(i >= 0)
{
d = (a * (((b * 2 - a) + MOD) % MOD)) % MOD;
e = ((a * a) % MOD + (b * b) % MOD) % MOD;
a = d;
b = e;
if(((n >> i) & 1) != 0)
{
c = (a + b) % MOD;
a = b;
b = c;
}
i--;
}
return a;
}
int main()
{
printf("%lld", F(1000000000));
}
使用前者的解决方案获得了TLE,而后者是AC。
现在,我的问题是:
有人可以向我解释迭代版本的执行吗?
答案 0 :(得分:1)
关于您的第一个问题:主要问题是您使用std::map
。插入和查找,因为std::map
implements a red-black tree(一种二叉搜索树)是O(log n)
。
std::map
是一个包含键值的有序关联容器 配有唯一键。使用比较对键进行排序 功能比较。搜索,删除和插入操作具有 对数复杂度。地图通常实现为红黑色 树。
(更不用说与STL的比较功能,根据我的经验,非常慢。)
因此,实际上,您的递归算法会调用另一个log n
算法log n
次,使其成为O(log n * log(log n))
。 (说BST是O(log m)
,其中m
是元素的数量log n
,它变为O(log(log n))
。)这本身并不是一个很大的区别,但是计算每次操作的成本相对较高。其中大部分归结为性能优化(或不优化)。此外,递归函数的计算迭代次数更多,因为它不像迭代函数那样严格二进制,即使两者具有相同的复杂度。
关于你的第二个问题:我不熟悉“快速加倍”算法背后的数学,所以我不能给出数学运算方式的逐行解释。如图所示,迭代算法的方法是仅通过使用2的幂的Fibonaccis而不是像递归算法那样划分来组成最终结果。它从LSB开始,并且向上运行。