星星之火OIer:斐波那契数列——番外篇(二)

在作者的上一篇博客中,我们提到了矩阵加速的另一道题

这次,我们来看一道LGOJ上的板题:P1939

一句话题意::a_1=a_2=a_3=1,a_i=a_i_-_1+a_i_-_3(i>3),求a_n

这道题是一道非常简单的矩阵加速题,可以用来练练手

我们可以很轻松的想到原数组:

A=\begin{bmatrix}a_1&a_2&a_3 \end{bmatrix}也就是\begin{bmatrix}a_i&a_i_+_1&a_i_+_2 \end{bmatrix}

那么我们要构造一个3*3的加速矩阵

首先,a_i直接可以变成a_i_+_1

所以加速矩阵的第一列就是\begin{bmatrix}0\\1\\0\end{bmatrix}

然后,a_i_+_1也可以直接由原数组得到a_i_+_2

所以加速矩阵的第二列就是\begin{bmatrix}0\\0\\1\end{bmatrix}

最后,a_i_+_2变到a_i_+_3需要他本身再加上a_i

所以最后一列就是\begin{bmatrix}1\\0\\1 \end{bmatrix}

综上::

A=\begin{bmatrix}a_1&a_2&a_3 \end{bmatrix}=\begin{bmatrix} 1&1&1\end{bmatrix},B=\begin{bmatrix}0&0&1\\1&0&0\\0&1&1 \end{bmatrix}

这就是这道题的基本结构

还要注意几个细节

  • 无限输入
  • 换行
  • n-1

自此我们可以给出代码::

#include<cstdio>
#include<cstring>
inline void read(long long &x) {
    x=0;
    long long f=1;
    char s=getchar();
    while(s<'0'||s>'9') {
        if(s=='-')
            f=-1;
        s=getchar();
    }
    while(s>='0'&&s<='9') {
        x=x*10+s-48;
        s=getchar();
    }
    x*=f;
}
inline void pr(long long x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9)
        pr(x/10);
    putchar(x%10+48);
}//快读快输不解释
long long A[3][3]= {{0,0,1},{1,0,0},{0,1,1}},n,m,ans[3]= {1,1,1},s[3];//构建加速矩阵、初始矩阵、答案矩阵
inline void cheng(long long a[3][3],long long b[3][3]) {//矩阵乘法运算
    long long c[3][3];
    memset(c,0,sizeof(c));//初始化
    for(long long i=0; i<3; i++)
        for(long long j=0; j<3; j++)
            for(long long k=0; k<3; k++)
                c[i][j]=(c[i][j]+a[i][k]%m*b[k][j]%m)%m;//矩阵乘法
    memcpy(a,c,sizeof(c));//传答案
}
inline void ksm(long long a[3][3],long long k) {//模拟快速幂
    if(k==1) {//只有一次
        memcpy(a,A,sizeof(A));//直接传
        return;
    }
    long long c[3][3];
    ksm(c,k/2);//除以二
    cheng(c,c);//平方
    if(k&1)//k为奇数
        cheng(c,A);
    memcpy(a,c,sizeof(c));//传答案
}
long long T;
int main() {
    read(T);
    m=1000000007;
    while(T--) {
        read(n);
        if(n==1||n==2)//特殊判断
            putchar(49),putchar('\n');//注意换行
        else {
            ksm(A,n-1);//加速
            for(long long j=0; j<3; j++)
                for(long long k=0; k<3; k++)
                    s[j]=(s[j]+ans[k]%m*A[k][j]%m)%m;//最后还要再乘一次
            pr(s[0]),putchar('\n');
        }
        A[0][0]=A[0][1]=A[1][1]=A[1][2]=A[2][0]=s[0]=s[1]=s[2]=0;
        A[0][2]=A[1][0]=A[2][1]=A[2][2]=ans[1]=ans[2]=ans[3]=1;//无限输入注意清零
    }
}

这就是这道题的全部代码

有错误的欢迎指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值