每日一题:斐波那锲数列

本文介绍了斐波那契数列及其在不同问题中的应用,包括台阶问题、牛的数量增长和放置砖块的问题。讨论了四种计算斐波那契数列的方法:递归、记忆化搜索、循环递推和矩阵快速幂,强调了优化计算效率和空间复杂度的重要性。文章最后预告了即将探讨的技术主题,如线段树和图论。

2020.10.13开始打卡每日一题

题目:斐波那锲数列

题目一:给定斐波那锲的前两项,求第n项斐波那锲数列的值,因为数字很大,我们对结果mod(1e9+7)

题目二:给定整数N,代表台阶数,一次可以跨2个或者1个台阶,求多少种走法

题目三:假设农场中成熟的母牛每年会生一头小母牛,并且永远不会死(就是这样)。第一年,农场只有一只成熟的母牛,从第二年开始,母牛开始生小母牛。每只小母牛三年之后成熟,又可以生小母牛。给定正数N,求N年后牛的数量。

题目四:给定一个2N的网格,现在有若干大小为12的板砖(只能竖着或者横着放,必),需要无缝紧密放到网格之中,返回多少种放法。

其实这些都是斐波那锲数列,非常有意思啊,哈哈哈哈哈哈。

计算的方法

递归

const int MOD = 1000000007;
int fabonacci(int n)
{
    if (n <= 1) return 1;
    return (f(n - 1) + f(n - 2)) % MOD;
}

显然,计算的节点个数是 O(2^n) 的级别的,存在大量重复计算。
时间复杂度是 O(2n)O(2n),一秒内大约能算到第三四十项。

剪枝

记录已经计算过的状态,每一次需要计算时查表,时间复杂度O(n)

const int N = 100000, MOD = 1000000007;
int a[N];//计算这个数有没有计算过
int fabonacci2(int n)
{
    if (a[n]) return a[n];
    if (n <= 1) return 1;
    a[n] = f2(n - 1) + f2(n - 2);
    a[n] %= MOD;
    return a[n];
}

递推

循环递推,时间复杂度还是O(n),但是需要开一个长度是n的数组,需要的内存是:

4 * n / (1024 * 1024)

const int N = 100000000, MOD = 1000000007;
int fabonacci3(int n)
{
    a[0] = a[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        a[i] = a[i - 1] + a[i - 2];
        a[i] %= MOD;
    }
    return a[n];
}

优化递推

其实我们只需要用前两项来计算后一项,那么如果我们只需要得到第n、项,不需要开一个大的数组。

时间复杂度不变,空间复杂度为O(1)

const int MOD = 1000000007;
int fabonacci4(int n)
{
    int x, y, z;
    x = y = 1;
    for (int i = 2; i <= n; i ++ )
    {
        z = (x + y) % MOD;
        x = y;
        y = z;
    }
    return z;
}

矩阵快速幂

我们知道快速幂可以用超快的速度计算出超过10^8级别的计算。通过找到项与项之间的关系,用矩阵的乘法去计算矩阵快速幂,次方可以用快速幂来优化。

矩阵快速幂:一个函数用来计算矩阵的乘法,另一个是计算矩阵的n次方。

const int N;
int temp[N][N];
//快速幂的模板
void multiplies(int a[][N], int b[][N], int c[][N])
{
    memset(temp,0,sizeof(temp));
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            for (int k = 0; k < N; k ++ )
            {
                long long x = temp[i][j] + (long long)a[i][k] * b[k][j];
                temp[i][j] = x % MOD;
            }
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            c[i][j] = temp[i][j];
}

int res[N][N];


int fastPower(int a[][N],long long n)
{
    //int x[2] = {1, 1};

    //int res[][2] = {{1, 0}, {0, 1}};
    memset(res,0,sizeof(res));
    for (int i = 0; i < n ; i++)
    {
        res[i][i]=1;
        //单位矩阵,res*任意一个矩阵都等于本身
    }
    //int t[][2] = {{1, 1}, {1, 0}};//转移矩阵
    //long long k = n - 1;
    while (n)
    {
        if (k&1) multiplies(res, a, res);
        multiplies(a, a, a);
        n >>= 1;
    }

快速幂的实现

这道题呢,需要计算出转移矩阵,然后计算n-1次方,

X1=【x1,x0】,那么Xn=【xn,xn-1】;

A=【1 1 1 0】;

Xn=Xn-1 * A;

递推得到:Xn=X1 * A^(n-1)

先计算A^n,然后再左乘一个X1,就能得到Xn,再取Xn的第一个元素就是答案

const int N;
const int MOD = 1000000007;

void multiplies(int a[][2], int b[][2], int c[][2])
{
    int temp[][2] = {{0, 0}, {0, 0}};
    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
            for (int k = 0; k < 2; k ++ )
            {
                long long x = temp[i][j] + (long long)a[i][k] * b[k][j];
                temp[i][j] = x % MOD;
            }
    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
            c[i][j] = temp[i][j];
}

int res[N][N];


int fastPower(long long n)
{
    int x[2] = {1, 1};

    //int res[][2] = {{1, 0}, {0, 1}};
    memset(res,0,sizeof(res));
    for (int i = 0; i < n ; i++)
    {
        res[i][i]=1;
        //单位矩阵,res*任意一个矩阵都等于本身
    }
    int t[][2] = {{1, 1}, {1, 0}};//转移矩阵
    long long k = n - 1;
    while (k)
    {
        if (k&1) multiplies(res, t, res);
        multiplies(t, t, t);
        k >>= 1;
    }

    int cur[2] = {0, 0};
    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
        {
            long long r = c[i] + (long long)x[j] * res[j][i];
            cur[i] = r % MOD;
        }

    return cur[0];
}

这其实模板题啊,懂原理学会运用就行。

近期预告:
线段树、树状数组、状压DP、Trie树、图论

如果大家有什么建议或者要求请后台留言
联系方式:shirandexiaowo@foxmail.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shirandexiaowo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值