【题解】递归数列

题目链接

题目大意:给定序列迭代规则,求一段的序列和。特点是要求的序列很长。

Solution

观察到,由于是求和,我们可以想到前缀和的思想。也就是说,对于求\(\sum_{i=m}^n a_i\),我们只需要求\(\sum_{i=1}^{m-1}a_i\)\(\sum_{i=1}^n a_i\),然后做差即可。

注意到\(n,m\)的范围,我们线性递推是不现实的。于是我们可以考虑矩阵快速幂。

考虑维护\(k+1\)个值。维护\(k\)个序列基本值,以及一个求和值。我们根据\(k\)个信息推出下一个信息。观察到\(k\)的范围,所以是可以做的。

构造矩阵比较简单。注意一下\(c\)的位置,是倒序的。

由于我们一次维护的是\(k\)个值,所以,如果我们要求\(pos\),则我们求出转移矩阵的\(pos-k\)次方就可以求出。

两次矩阵快速幂加上前缀和思想,这题做完了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
ll n,m,p,s,ss[16],Ans,sss;
inline ll add(ll x,ll y){return (x%p+y%p)%p;}
inline ll mul(ll x,ll y){return (x%p*y%p)%p;}
inline ll del(ll x,ll y){return x-y<0?x+p-y:x-y;}
int k,b[16],c[16];
struct Mat{
    ll A[17][17];
    Mat(){memset(A,0,sizeof(A));}
    Mat operator*(const Mat&B)const{
        Mat res;
        for(int i=1;i<=k+1;++i)
            for(int j=1;j<=k+1;++j)
                for(int l=1;l<=k+1;++l)
                    res.A[i][j]=add(res.A[i][j],mul(A[i][l],B.A[l][j]));
        return res;
    }
}w,s1,A2,A1;
inline void Init(Mat &x){for(int i=1;i<=k+1;++i)x.A[i][i]=1;}
Mat qpow(Mat st,ll b){
    Mat c;
    Init(c);
    while(b){
        if(b&1)c=c*st;
        b>>=1;st=st*st;
    }
    return c*s1;
}
signed main(){
    scanf("%lld",&k);
    for(int i=1;i<=k;++i)scanf("%lld",&b[i]);
    for(int i=1;i<=k;++i)scanf("%lld",&c[i]);
    scanf("%lld%lld%lld",&m,&n,&p);
    for(int i=1;i<=k;++i)ss[i]=add(ss[i-1],(ll)b[i]);
    for(int i=1;i<=k;++i)s1.A[i][1]=b[i];
    s1.A[k+1][1]=ss[k];
    for(int i=1;i<k;++i)w.A[i][i+1]=1;
    for(int i=1;i<=k;++i)w.A[k][i]=c[k-i+1],w.A[k+1][i]=c[k-i+1];
    w.A[k+1][k+1]=1;
    if(m-1>=k)A1=qpow(w,m-1-k);
    else A1.A[k+1][1]=ss[m-1];
    if(n>=k)A2=qpow(w,n-k);
    else A2.A[k+1][1]=ss[n];
    Ans=del(A2.A[k+1][1],A1.A[k+1][1]);
    printf("%lld\n",Ans);
    return 0;
} 

猜你喜欢

转载自www.cnblogs.com/h-lka/p/12353345.html