斐波那契数列-题解

原题出自Ural 1133

题目大意:扩展斐波那契数列在整数集上的定义,即不是从 F0=0 F1=1 这样子开始的,下标可以是任意整数。给定这样一个序列中的两个下标,i,j,以及他们在序列中对应的值 Fi Fj ,然后要求 Fn (n是输入的一个数字)。

思路1:由于题目里说到数据范围在

2109Fk2109(k=min(i,j,n),,max(i,j,n)).

所以可以考虑直接二分查找 Fi+1 (在下面有分析)就好了(很容易验证这个序列的单调性)。

思路2:观察系数规律,i,j,n中的最小者是i,那么:
Fi+2=Fi+1+Fi
Fi+3=Fi+2+Fi+1=2Fi+1+Fi
Fi+4=Fi+3+Fi+2=3Fi+1+2Fi

Fi+n=fnFi+1+fn1Fi .
其中 fn 是常规的斐波那契数列:{0, 1, 1, 2, 3, 5, 8, 13, 21, …, },这个规律我称之为“系数规律”。

现在只看i和j,假设j比i大,那么:
Fi,Fi+1,Fi+2,...,Fj ,有了“系数规律”,只需要求解 Fi+1 就好了,一旦知道了 Fi+1 ,那么就可以直接算出 Fn 了。怎么算出n呢?假设n比i大,那么根据上面推导的“系数规律”,有:

Fn=Fi+(ni)=fniFi+1+fni1Fi.

所以关键是求出 Fi+1 ,那么怎么求呢?还是根据“系数规律”!!!我们已知 Fi Fj ,那么就有:
Fj=Fi+(ji)=fjiFi+1+fji1Fi.(1)

注意到方程(1)只有一个未知数 Fi+1 ,很容易求出:
Fi+1=(Fifji1Fi)/Fji.

至此,貌似一切问题都解决了,不过还有两个小问题:
1)如果n比i小?(反过来推就好了,知道 Fi Fi+1 之后)
2)需要预处理先求出斐波那契,但是这道题最多要计算到 f2000 ,不会爆吗?试试C++的long double咯,反正我过了,或者任性的,可以直接用python╮(╯▽╰)╭。

贴代码如下:

#include <iostream>
using namespace std;

typedef long double LD;
const int MAX = 2005;
LD f[MAX];

int main() {
    // 预处理f数组
    f[0] = 0, f[1] = 1;
    for (int i = 2; i < MAX; ++i)
        f[i] = f[i-1] + f[i-2];

    int i, j, n;
    LD Fi, Fj, Fn, Fii;
    cin >> i >> Fi >> j >> Fj >> n;

    if (i > j) {
        swap(i, j);
        swap(Fi, Fj);
    }

    // 因为数组的下标应该是非负的
    i += 1000, j += 1000, n += 1000;

    // 求出F(i+1)
    Fii = (j - i == 1) ? Fj : ((Fj - f[j-i-1]*Fi) / f[j-i]);

    if (n == i)
        Fn = Fi;
    else if (n - i == 1)
        Fn = Fii;
    else if (n - i > 1) {
        // 注意不能直接用公式:Fn = f[n-i]*Fii + f[n-i-1]*Fi,中间的乘法可能会爆
        for (int index = i+2; index <= n; ++index) {
            Fn = Fii + Fi;
            Fi = Fii;
            Fii = Fn;
        }
    }
    else {
        for (int index=i-1; index >= n; --index) {
            Fn = Fii - Fi;
            Fii = Fi;
            Fi = Fn;
        }
    }

    cout << (int)Fn << endl;

    return 0;
}

运行结果是:
我的AC

好吧,虽然有点丑,但我觉得还是把算法说到位了。
最后再说一句,这道题这么敏感的计算,再次演示了“编译器有毒”系列作品,用Visual C++ 2013的人目测都会死得很惨,因为我就是用它,然后一直WA 10了,改用G++ 4.9就好了╮(╯▽╰)╭。。。。。

最后再贴我用python 3的代码(矩阵快速幂+二分):

# URAL 1133
Unit = [[1, 1], [1, 0]]

def mul(a, b):
    ans = [[0 for i in range(0, len(a))] for j in range(0, len(b))]
    for i in range(0, len(a)):
        for j in range(0, len(b)):
            for k in range(0, len(b)):
                ans[i][j] += a[i][k] * b[k][j]
    return ans

def POW(n):
    '''注意不能写n /= 2,会被python当做浮点数,应该是用位运算'''
    a = Unit
    b = [[1, 0], [0, 1]]
    while n > 0:
        if n & 1:
            b = mul(a, b)
        a = mul(a, a)
        n >>= 1
    return b

def cal(i, Fi, j, Fj, n):
    mat = POW(j-1)
    [Left, Right] = [int(-2*1e9), int(2*1e9)]
    while Left <= Right:
        mid = (Left + Right) >> 1
        x = mat[0][0] * mid + mat[0][1] * Fi
        if x > Fj:
            Right = mid - 1
        elif x < Fj:
            Left = mid + 1
        else:
            break

    if n > 0:
        mat = POW(n - 1)
        print(mid*mat[0][0] + Fi*mat[0][1])
    else:
        [Fa, Fb] = [mid, Fi]
        for i in range(n, 0):
            [Fa, Fb] = [Fb, Fa - Fb]
        print(Fb)


if __name__ == '__main__':
    [i, Fi, j, Fj, n] = [int(i) for i in input().split(' ')]
    if i > j:
        [i, j] = [j, i]
        [Fi, Fj] = [Fj, Fi]
    j -= i
    n -= i
    i = 0
    cal(i, Fi, j, Fj, n)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值