中国(北方)大学生程序设计训练赛(第一周)E. water problem

函数 f:Z+Zf:Z+→Z。已知 f(1)f(2)f(1),f(2) 的值,且对于任意 x>1x>1,有 f(x+1)=f(x)+f(x1)+sin(πx2)f(x+1)=f(x)+f(x−1)+sin⁡(πx2)

求 f(n)f(n的值

多组数据。(数据组数 T100T≤100

每组数据包含 33 个不超过 109109 的正整数,分别代表 f(1)f(2)f(1),f(2) 和 nn 的值。

输出  f(n)mod(109+7)f(n)mod(109+7) 。每组输出末尾有换行符。


题意:

题目类似斐波那契数列,但比斐波那契数列多了一项。

思路:

首先回忆斐波那契数列的计算方法:

一、递归

直接按照递推公式递归运算,此方法有大量重复计算,导致n趋近不足100就无法在有限时间内计算出来,通常作为递归讲解的例子。

二、动态规划(dp)

为了避免重复计算,我们通常用一个数组记录已经计算的值,采用记忆化搜索的动态规划,就可以减少重复计算。

根据dp[n] = dp[n-1] + dp[n-2] 从第三项开始迭代即可。

此方法的时间复杂度为O(n),线性时间复杂度,考虑在1s内,无法计算大于1e8的数据范围。

三、矩阵快速幂

数列的递推公式为:f(1)=1,f(2)=2,f(n)=f(n-1)+f(n-2)(n>=3)

   用矩阵表示为:

  进一步,可以得出直接推导公式:

所以,对矩阵[1,1,1,0]进行n-2次幂运算,就可以计算出f(n)的值,此时由于幂运算可以使用快速幂运算,可以用矩阵快速幂来进行计算。

此时的时间复杂度为O(logN)

对比斐波那契数列,将其推广

由于本题中的递推关系非线性,即有sin项的约束,f(x+1)=f(x)+f(x1)+sin(πx2)f(x+1)=f(x)+f(x−1)+sin⁡(πx2)。我们展开两项,得到f(x) = f(x-1) + f(x-3) + f(x-4),通过sin的周期性,消去了常数项。

得到了一个四阶线性递推公式。

类比斐波那契数列的方法,我们需要找一个转移矩阵,满足上述矩阵运算。

由于斐波那契数列是二阶递推数列,所以存在一个2*2的矩阵A,使得:

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

所以以上的四阶递推公式也一定存在一个4*4的矩阵A,满足

[Fn Fn-1 Fn-2 … Fn-k+1] = A*[Fn-1 Fn-2 Fn-3 … Fn-k]

                                          = …

                                          = An-k+1 *[Fk-1 Fk-2 Fk-3 … F0]    

这里的k=5;

根据以上地推关系,拼凑出矩阵A(转移矩阵) 为[1 0 1 1,1 0 0 0,0 1 0 0,0 0 1 0]

在计算矩阵的幂时,首先我们定义矩阵乘法的运算法则(矩阵乘法公式),然后对指数进行快速幂处理,通过减少乘法次数,化时间为对数量级。

代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int mod = 1000000007;
long long s1[4][4] = {
{1,0,1,1},{1,0,0,0},{0,1,0,0},{0,0,1,0}
};
long long s2[4][4] = {
{1,0,0,0}, {0,1,0,0},{0,0,1,0},{0,0,0,1}
};
struct mat
{
    long long m[4][4];
};
mat origin;
mat res;
mat multiply(mat temp1,  mat temp2)
{
    mat r;
    for(int i = 0; i < 4; ++i)
        for(int j = 0; j < 4; ++j)
        {
            r.m[i][j] = 0;
            for(int k = 0; k < 4; ++k)
                r.m[i][j] = (r.m[i][j] + ((temp1.m[i][k]*temp2.m[k][j])%mod))%mod;
        }
    return r;
}
mat calc(int n)
{

     while(n)
     {
        if(n&1)
            res=multiply(res,origin);
        n>>=1;
        origin=multiply(origin,origin);
     }
     return res;
}
int a, b, n;
int main()
{
    while(cin >> a >> b >> n)
    {
        for(int i = 0; i < 4; ++i)
            for(int j = 0; j < 4; ++j)
            {
                origin.m[i][j] = s1[i][j];
                res.m[i][j] = s2[i][j];
            }

        long long res;
        if(n == 1)
        {
            res = a;
        }
        else if(n == 2)
            res = b;
        else if(n == 3)
            res = a+b;
        else if(n == 4)
            res = a+2*b-1;
        else
        {
            mat r = calc(n-4);
            res = ( (r.m[0][0]*(a+2*b-1))%mod + (r.m[0][1]*(a+b))%mod + (r.m[0][2]*b)%mod +(r.m[0][3]*a)%mod )%mod;
        }
        cout << res << endl;
    }
}

个人记录一下学习历程,如有不足或者错误之处,请多多指点。谢谢


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值