《logN的时间内算fibonacci number》

来源:互联网 发布:nginx php 优化 编辑:程序博客网 时间:2024/05/16 08:10

logN的时间内算fibonacci number

 

三、矩阵法

Fibonacci数精确值的最快的方法应该就是矩阵法,看过的人都觉得这个方法很好。如果你跟我一样,曾经为记住这个方法中的矩阵而烦恼,那今天就来看看怎么进行推导。其实方法非常简单,想清楚了也就自然而然地记住了。

我们把Fibonacci数列中相邻的两项:F(n)F(n - 1)写成一个2x1的矩阵,然后对其进行变形,看能得到什么:

[FnFn1]=[Fn1+Fn2Fn1]=[1×Fn1+1×Fn21×Fn1+0×Fn2]=[1110]×[Fn1Fn2]

是不是非常自然呢?把等式最右边继续算下去,最后得到:

[FnFn1]=[1110]n1×[F1F0]=[1110]n1×[10]

因此要求F(n),只要对这个二阶方阵求n - 1次方,最后取结果方阵第一行第一列的数字就可以了。

看起来有点儿化简为繁的感觉,但关键点在于,幂运算是可以二分加速的。设有一个方阵a,利用分治法求an次方,有:

an={an/2×an/2a(n1)/2×a(n1)/2×a,if x iseven, if x isodd

可见复杂度满足T(n) = T(n / 2) +O(1),根据Master定理可得:T(n) = O(log n)

在实现的时候,可以用循环代替递归实现这里的二分分治,好处是降低了空间复杂度(用递归的话,空间复杂度为O(log n))。下面的Python程序直接利用的numpy库中的矩阵乘法(当然这个库也实现了矩阵的幂运算,我把它单独写出来是为了强调这里的分治算法)。

 

斐波那契数列Fibonacci

http://blog.csdn.net/ju136/article/details/8062078

  • 最简单的处理方式

当n<=50的时候,是可以利用64位int长整型。long long来进行处理。

[cpp] view plaincopy

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3.   
  4. long long a[71]; 
  5. void init(void) { 
  6.     a[0] = 0; 
  7.     a[1] = 1; a[2] = 2; 
  8.     for (int i = 3; i < 71; ++i) { 
  9.         a[i] = a[i-1] + a[i-2]; 
  10.     } 
  11.   
  12. int main(void) { 
  13.     int n;init(); 
  14.     while (scanf("%d", &n) != EOF) { 
  15.         printf("%lld\n", a[n]); 
  16.     } 
  17.     return 0; 
  18. //注意打表及长整型。 

很简单,就把上面那个题给秒掉了。

 

高效的处理

如果你还对前面讲到的斐波那契数有印象的话,可以轻易地得到。

[ FnFn-1] = [F2 F1] pow(A, n - 2);

A =[1 1

       1 0];

 

值得注意的是,针对于原始的斐波那契数而言,其递推之后是到[F1 F0] 截止,所以其指数是n-1。而在这里是到[F2F1] 截止。所以指数为n-2

此外,假设T = pow(A, n-2);

与前面不同的是,Fn = F2*T[0][0] + F1*T[1][0];

从而可以写出如下代码:

 

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3.   
  4. typedef struct _node 
  5.     long long a, b; 
  6.     long long c, d; 
  7. } node; 
  8.   
  9.   
  10. void _multiple(node *x, node *y) { 
  11.     node temp; 
  12.     temp.a = x->a*y->a + x->b*y->c; 
  13.     temp.b = x->a*y->b + x->b*y->d; 
  14.     temp.c = x->c*y->a + x->d*y->c; 
  15.     temp.d = x->c*y->b + x->d*y->d; 
  16.     *x = temp; 
  17.   
  18. long long fib(int n) 
  19.     if (0 == n) return 0; 
  20.     if (1 == n) return 1; 
  21.     if (2 == n) return 2; 
  22.   
  23.     node odd; odd.a = odd.d = 1; odd.c = odd.b = 0; //单位矩阵 
  24.     node temp; temp.a = temp.b = temp.c = 1; temp.d = 0; // A矩阵 
  25.   
  26.     n -= 2; 
  27.     while (n) { 
  28.         if (n&1) _multiple(&odd, &temp); 
  29.         _multiple(&temp, &temp); 
  30.         n >>= 1; 
  31.     } 
  32.   
  33.     return odd.a * 2 + odd.c; 
  34.   
  35. int main() 
  36.     int n; 
  37.     while (scanf("%d", &n) != EOF) { 
  38.         printf("%lld\n", fib(n)); 
  39.     } 
  40.     return 0; 


大整数的处理

这里,还是可以利用前面提到的大整数的加法模板来进行运算。

[cpp] view plaincopy

 

  1. #include <iostream> 
  2. #include <string> 
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. using namespace std; 
  6.   
  7. string &_del_zeros_before_dot(string &a) 
  8.     if (a.length() <= 0 || a[0] != '0'return a; 
  9.     int i = 0; 
  10.     while (i < a.length() && a[i] == '0') ++i; 
  11.     a = a.substr(i, a.length() - i); 
  12.     return a; 
  13.   
  14. string &_string_add_string(const string &a, const string &b, string &res) 
  15.     int sum_value = 0, add_bit = 0; 
  16.     const int alen = a.length(), blen = b.length(); 
  17.     res = "0" + (alen > blen ? a : b); 
  18.     for (int i = alen-1, j = blen-1, k = res.length() - 1;  
  19.          i >= 0 || j >= 0 || add_bit > 0;  
  20.          --i, --j, --k){ 
  21.         sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit; 
  22.         add_bit = sum_value / 10;  
  23.         res[k] = sum_value%10 + '0'
  24.     } 
  25.     if (res[0] == '0') res = res.substr(1, res.length() - 1); 
  26.     return res; 
  27.   
  28. string fib(int n) { 
  29.     if (0 == n) return "0"
  30.     if (1 == n) return "1"
  31.     if (2 == n) return "2"
  32.     string a_2 = "1", b_1 = "2", ret = "0"
  33.     for (int i = 3; i <= n; ++i) { 
  34.         _string_add_string(a_2, b_1, ret); // fn = fn-2 + fn-1; 
  35.         a_2 = b_1; 
  36.         b_1 = ret; 
  37.     } 
  38.     return ret; 
  39.   
  40. int main(void) { 
  41.     int n; 
  42.     while (scanf("%d", &n) != EOF) { 
  43.         printf("%s\n", fib(n).c_str()); 
  44.     } 
  45.     return 0; 


扩展-1

首先题意变成:如果一下子可以走123步呢。也就是多了一种走法。根据惯性思维

Fn = Fn-1 + Fn-2 + Fn-3

F1 = 1

F2 = 2

F3 = F1 + F2 + 1 = 4

由于有了递推公式。可以轻易地写出如下代码:

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2.   
  3. long long step(int n) { 
  4.     int i = 0; 
  5.     long long a = 1, b = 2, c = 4, ret; 
  6.     if (0 == n) return 0; 
  7.     if (1 == n) return 1; 
  8.     if (2 == n) return 2; 
  9.     if (3 == n) return 4; 
  10.     for (i = 4; i <= n; ++i) { 
  11.         ret = a + b + c; 
  12.         a = b; 
  13.         b = c; 
  14.         c = ret; 
  15.     } 
  16.     return ret; 
  17.   
  18. int main(void
  19.     int n; 
  20.     while (scanf("%d", &n) != EOF) { 
  21.         printf("%lld\n", step(n)); 
  22.     } 
  23.     return 0; 


同样也可以写成大数模板形式

[cpp] view plaincopy

 

  1. #include <iostream> 
  2. #include <string> 
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. using namespace std; 
  6.   
  7. string &_del_zeros_before_dot(string &a) 
  8.     if (a.length() <= 0 || a[0] != '0'return a; 
  9.     int i = 0; 
  10.     while (i < a.length() && a[i] == '0') ++i; 
  11.     a = a.substr(i, a.length() - i); 
  12.     return a; 
  13.   
  14. string &_string_add_string(const string &a, const string &b, string &res) 
  15.     int sum_value = 0, add_bit = 0; 
  16.     const int alen = a.length(), blen = b.length(); 
  17.     res = "0" + (alen > blen ? a : b); 
  18.     for (int i = alen-1, j = blen-1, k = res.length() - 1;  
  19.          i >= 0 || j >= 0 || add_bit > 0;  
  20.          --i, --j, --k){ 
  21.         sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit; 
  22.         add_bit = sum_value / 10;  
  23.         res[k] = sum_value%10 + '0'
  24.     } 
  25.     if (res[0] == '0') res = res.substr(1, res.length() - 1); 
  26.     return res; 
  27.   
  28. string fib(int n) { 
  29.     if (0 == n) return "0"
  30.     if (1 == n) return "1"
  31.     if (2 == n) return "2"
  32.     if (3 == n) return "4"
  33.     string a = "1", b = "2", c = "4", ret = "0", temp="0"
  34.     for (int i = 4; i <= n; ++i) { 
  35.         _string_add_string(a, b, temp); // temp = fn-2 + fn-3; 
  36.         _string_add_string(temp, c, ret); // ret = temp + fn-1; 
  37.         a = b; 
  38.         b = c; 
  39.         c = ret; 
  40.     } 
  41.     return ret; 
  42.   
  43. int main(void) { 
  44.     int n; 
  45.     while (scanf("%d", &n) != EOF) { 
  46.         printf("%s\n", fib(n).c_str()); 
  47.     } 
  48.     return 0; 


那么我们考虑,如何写成O(lgN)的效率呢。

同样,我们也可以写出矩阵的表达式。

[Fn Fn-1 Fn-2] = [Fn-1Fn-2 Fn3] * A;

可以得出A的值为

1  1   0

1  0   1

1  0   0

那么,在这种情况下,如果层次递推,我们可以得到

[Fn Fn-1 Fn-2] = [F3 F2F1] * pow(A, n-3);

如果设T = pow(A, n-3);

那么Fn = F3*T[0,0] + F2*T[1,0] + F1*T[2,0];

 

因此,也可以写出如下的代码。

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <string.h> 
  4. #define MX 3 
  5.   
  6. typedef struct _node 
  7.     long long a[MX][MX]; 
  8. } node; 
  9.   
  10.   
  11. void _multiple(node *x, node *y) { 
  12.     node temp; 
  13.     long long (*a)[MX] = x->a; 
  14.     long long (*b)[MX] = y->a; 
  15.     long long (*c)[MX] = temp.a; 
  16.   
  17.     for (int row = 0; row < MX; ++row) {      
  18.         for (int col = 0; col < MX; ++col) { 
  19.             long long s = 0; 
  20.             for (int i = 0; i < MX; ++i) { 
  21.                 s += a[row][i] * b[i][col]; 
  22.             } 
  23.             c[row][col] = s; 
  24.         } 
  25.     } 
  26.     *x = temp; 
  27.   
  28. long long fib(int n) 
  29.     if (0 == n) return 0; 
  30.     if (1 == n) return 1; 
  31.     if (2 == n) return 2; 
  32.     if (3 == n) return 4; 
  33.   
  34.     node odd; 
  35.     //设置odd为单位矩阵 
  36.      memset(odd.a, 0, sizeof(odd.a)); 
  37.     for (int i = 0; i < MX; ++i) { 
  38.         odd.a[i][i] = 1; 
  39.     } 
  40.     node temp; // A矩阵 
  41.     memset(temp.a, 0, sizeof(temp.a)); 
  42.     for (int i = 0; i < MX; ++i) { 
  43.         temp.a[i][0] = 1; 
  44.         if (i + 1 < MX) temp.a[i][i+1] = 1; 
  45.     } 
  46.   
  47.     n -= 3; 
  48.     while (n) { 
  49.         if (n&1) _multiple(&odd, &temp); 
  50.         _multiple(&temp, &temp); 
  51.         n >>= 1; 
  52.     } 
  53.     return 4*odd.a[0][0] + 2*odd.a[1][0] + odd.a[2][0]; 
  54.   
  55. int main() 
  56.     int n; 
  57.     while (scanf("%d", &n) != EOF) { 
  58.         printf("%lld\n", fib(n)); 
  59.     } 
  60.     return 0; 


扩展-2

那么接下的问题是

假设A上台阶,一次可以跨1层,2层,3..m层,问An层台阶,有多少种走法?

其中,mn都是正整数,并且 m<= n, m <= 10, n <= 50

由于m, n并不清楚。

 

不过对于1<= x <= m的走法,应该是很清楚的。也就是Fx = Fx-1 + Fx-2 + ...... + F1 + 1

而对于大于m的而言,则是Fy= Fy-1 + Fy-2 + Fy-3 + .... + Fy-m;

 

公式都出来了,就不用去想了。

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2. #include <iostream> 
  3. #include <list> 
  4.   
  5. using namespace std; 
  6.   
  7. long long fib(unsigned int m, int n) { 
  8.     list<long long> l; 
  9.     long long a = 0, s = 0, ret; 
  10.     for (int i = 1; i <= m && i <= n; ++i) { 
  11.         a = s + 1; 
  12.         l.push_back(a); 
  13.         s += a; 
  14.     } 
  15.     if (n <= m) return l.back(); 
  16.   
  17.     for (int i = m + 1; i <= n; ++i) { 
  18.         s = 0; 
  19.         for (list<long long>::iterator iter = l.begin();  
  20.             iter != l.end(); ++iter) { 
  21.             s += *iter; 
  22.         } 
  23.         ret = s; 
  24.   
  25.         l.pop_front(); 
  26.         l.push_back(ret); 
  27.     } 
  28.     return ret; 
  29.   
  30. int main(void) { 
  31.     int m, n; 
  32.     while (scanf("%d%d", &m, &n) != EOF){ 
  33.         printf("%lld\n", fib(m, n)); 
  34.     } 
  35.     return 0; 


如果觉得上面的方法不好理解。没关系。直接采用打表法。

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2. int m, n; 
  3. long long a[51]; 
  4. long long fib(int m, int n) { 
  5.     long long s = 0; 
  6.     a[0] = 0; 
  7.     for (int i = 1; i <=m && i <= n; ++i) { 
  8.         a[i] = s + 1; 
  9.         s += a[i]; 
  10.     } 
  11.     if (n <= m) return a[n]; 
  12.     for (int i = m + 1; i <= n; ++i) { 
  13.         s = 0; 
  14.         for (int j = 1; j <= m; ++j) { 
  15.             s += a[i-j]; 
  16.         } 
  17.         a[i] = s; 
  18.     } 
  19.     return a[n]; 
  20. int main(void
  21.     while (scanf("%d%d", &m, &n) != EOF &&0 <= m && m <=n && n <= 50) { 
  22.         printf("%lld\n", fib(m, n)); 
  23.     } 
  24.     return 0; 

这里也顺便给出大整数求解的方案,也就是当n超过50的时候的处理方式。

[cpp] view plaincopy

 

  1. #include <iostream> 
  2. #include <string> 
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. #include <list> 
  6. using namespace std; 
  7.   
  8. string &_del_zeros_before_dot(string &a) 
  9.     if (a.length() <= 0 || a[0] != '0'return a; 
  10.     int i = 0; 
  11.     while (i < a.length() && a[i] == '0') ++i; 
  12.     a = a.substr(i, a.length() - i); 
  13.     return a; 
  14.   
  15. string &_string_add_string(const string &a, const string &b, string &res) 
  16.     int sum_value = 0, add_bit = 0; 
  17.     const int alen = a.length(), blen = b.length(); 
  18.     res = "0" + (alen > blen ? a : b); 
  19.     for (int i = alen-1, j = blen-1, k = res.length() - 1;  
  20.          i >= 0 || j >= 0 || add_bit > 0;  
  21.          --i, --j, --k){ 
  22.         sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit; 
  23.         add_bit = sum_value / 10;  
  24.         res[k] = sum_value%10 + '0'
  25.     } 
  26.     if (res[0] == '0') res = res.substr(1, res.length() - 1); 
  27.     return res; 
  28.   
  29. string fib(unsigned int m, int n) { 
  30.     list<string> l; 
  31.     string a = "0", s = "0", ret, temp; 
  32.     for (int i = 1; i <= m && i <= n; ++i) { 
  33.         _string_add_string(s, "1", a); 
  34.         l.push_back(a); 
  35.         _string_add_string(s, a, temp); 
  36.         s = temp; 
  37.     } 
  38.     if (n <= m) return l.back(); 
  39.   
  40.     for (int i = m + 1; i <= n; ++i) { 
  41.         s = "0"
  42.         for (list<string>::iterator iter = l.begin();  
  43.             iter != l.end(); ++iter) { 
  44.             _string_add_string(s, *iter, temp); 
  45.             s = temp; 
  46.         } 
  47.         ret = s; 
  48.   
  49.         l.pop_front(); 
  50.         l.push_back(ret); 
  51.     } 
  52.     return ret; 
  53.   
  54. int main(void) { 
  55.     int m, n; 
  56.     while (scanf("%d%d", &m, &n) != EOF) { 
  57.         printf("%s\n", fib(m, n).c_str()); 
  58.     } 
  59.     return 0; 


可能你还希望找出一个针对于存在m的情况下的高效率的解法。那好吧。这里给个示例。原理与前面O(lgN)的原理几乎一样。

只不过你需要注意一下A矩阵的表达形式。其他的没什么不一样的。还有就是最后有个相乘的情况。

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <string.h> 
  4.   
  5. typedef struct _node { 
  6.     long long **a; 
  7. }node; 
  8. long long **_init(int m) { 
  9.     long long **t = NULL; 
  10.     t = (long long **)malloc(sizeof(long long*)*m); 
  11.     for (int i = 0; i < m; ++i){ 
  12.         t[i] = (long long *)malloc(sizeof(long long) * m); 
  13.         for (int j = 0; j < m; ++j) { 
  14.             t[i][j] = 0; 
  15.         } 
  16.     } 
  17.     return t; 
  18.   
  19. void _destroy(long long **t, int m) { 
  20.     for (int i = 0; i < m; ++i) { 
  21.         free(t[i]); 
  22.     } 
  23.     free(t); 
  24.   
  25. void _multiple(node *x, node *y, int m) { 
  26.     node temp; temp.a = _init(m); 
  27.     long long **a = x->a; 
  28.     long long **b = y->a; 
  29.     long long **c = temp.a; 
  30.   
  31.     for (int row = 0; row < m; ++row) {       
  32.         for (int col = 0; col < m; ++col) { 
  33.             long long s = 0; 
  34.             for (int i = 0; i < m; ++i) { 
  35.                 s += a[row][i] * b[i][col]; 
  36.             } 
  37.             c[row][col] = s; 
  38.         } 
  39.     } 
  40.     _destroy(x->a, m); 
  41.     x->a = temp.a; 
  42.   
  43. long long fib(int m, int n) 
  44.     long long ret; 
  45.     long long *front = (long long *) malloc(sizeof(long long)*(m + 1)); 
  46.   
  47.     long long s = 0;  
  48.     front[0] = 0; 
  49.     for (int i = 1; i <= m && i <= n; ++i) { 
  50.         front[i] = s + 1; 
  51.         s += front[i]; 
  52.     } 
  53.   
  54.     if (n <= m) { 
  55.         ret = front[n]; 
  56.         free(front); 
  57.         return ret; 
  58.     } 
  59.   
  60.     node odd; odd.a = _init(m); 
  61.     //设置odd为单位矩阵 
  62.     for (int i = 0; i < m; ++i) { 
  63.         odd.a[i][i] = 1; 
  64.     } 
  65.     //A矩阵. 
  66.     node temp; temp.a = _init(m);  
  67.     for (int i = 0; i < m; ++i) { 
  68.         temp.a[i][0] = 1; 
  69.         if (i + 1 < m) temp.a[i][i+1] = 1; 
  70.     } 
  71.   
  72.     n -= m; 
  73.     while (n) { 
  74.         if (n&1) _multiple(&odd, &temp, m); 
  75.         _multiple(&temp, &temp, m); 
  76.         n >>= 1; 
  77.     } 
  78.   
  79.     ret = 0; 
  80.     for (int i = 1; i <= m; ++i) { 
  81.         ret += front[i] * odd.a[m-i][0]; 
  82.     } 
  83.   
  84.     _destroy(odd.a, m); 
  85.     _destroy(temp.a, m); 
  86.     return ret; 
  87.   
  88. int main() 
  89.     int m, n; 
  90.     while (scanf("%d%d", &m, &n) != EOF) { 
  91.         printf("%lld\n", fib(m, n)); 
  92.     } 
  93.     return 0; 


扩展-3

一只青蛙一次可以跳上1级台阶,也可以跳上2……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

测试链接:http://ac.jobdu.com/problem.php?cid=1039&pid=5
 

这个只是第二个扩展的特殊情况。也就是当m==n的时候的情况。

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <string.h> 
  4.   
  5. long long fib(int m) 
  6.     long long ret; 
  7.     long long *front = (long long *) malloc(sizeof(long long)*(m + 1)); 
  8.   
  9.     long long s = 0;  
  10.     front[0] = 0; 
  11.     for (int i = 1; i <= m; ++i) { 
  12.         front[i] = s + 1; 
  13.         s += front[i]; 
  14.     } 
  15.     ret = front[m]; 
  16.     free(front); 
  17.     return ret; 
  18.   
  19. int main() 
  20.     int m, n; 
  21.     while (scanf("%d", &n) != EOF) { 
  22.         printf("%lld\n", fib(n)); 
  23.     } 
  24.     return 0; 


这个就没有更高效的办法了。除了再加入大整数之外。~~大整数就不用再加了。都差不多的。

 

 当然,如果再深入地看一下,会发现F(n) = 2^(n-1);

那这个代码就更好写了。

[cpp] view plaincopy

 

  1. #include <stdio.h> 
  2.   
  3. long long pow(long long x, long long n) { 
  4.     long odd = 1; 
  5.     while (n) { 
  6.         if (n&1) odd *= x; 
  7.         x *= x; 
  8.         n >>= 1; 
  9.     } 
  10.     return odd; 
  11.   
  12. int main(void) { 
  13.     int n; 
  14.     while (scanf("%d", &n) != EOF) { 
  15.         printf("%lld\n", pow(2, n-1));  
  16.     } 
  17.     return 0; 

 

 

0 0