【POJ2118】Firepersons-递推+矩阵乘法

本文介绍了一种优化常系数线性递推关系求解的方法,通过改进矩阵乘法过程达到O(k²logi)的时间复杂度,同时探讨了进一步优化的可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试地址:Firepersons
题目大意:求解常系数线性递推关系: an=ki=1anibi a n = ∑ i = 1 k a n − i b i 的第 i i 项(ai),其中 a0,...,ak1 a 0 , . . . , a k − 1 b1,...,bk b 1 , . . . , b k 已给出。
做法:本题需要用到递推+矩阵乘法。
这题 i109,k100 i ≤ 10 9 , k ≤ 100 ,很容易想到用矩阵乘法做到 O(k3logi) O ( k 3 log ⁡ i ) 的复杂度,那么这题就做完了。
……但是有没有更好的做法呢?
令转移矩阵为 M M ,我们最后要求的是Mik+1X(其中 X X 为列向量,从下往上排列着a0,...,ak1)。根据一些奇妙的线性代数结论,由特征多项式以及一些奇妙定理可以得到:
对于 i0 i ≥ 0 ,有 Mi+k=kj=1bjMi+kj M i + k = ∑ j = 1 k b j M i + k − j
有了这个东西,有什么用呢?我们可以得到一个重要的结论:所有 Mi M i 都可以表示成 M0,...,Mk1 M 0 , . . . , M k − 1 的线性组合。这个可以用数学归纳法简单证明。
于是我们在原先矩阵快速幂的基础上,将两个 M M 的幂乘起来的过程原先是需要O(k3)的,但现在我们只需要做一个多项式乘法即可,时间复杂度为 O(k2) O ( k 2 ) 。但这样得出来的是 M0,...,M2k2 M 0 , . . . , M 2 k − 2 的线性组合,那么只要用上面那个结论把高于 k1 k − 1 次的项往下规约即可,也是 O(k2) O ( k 2 ) 的,因此整个算法的时间复杂度为 O(k2logi) O ( k 2 log ⁡ i ) ,可以做到 k=2000 k = 2000 左右的水平。
据说用FFT优化多项式乘法等可以优化到 O(klogklogi) O ( k log ⁡ k log ⁡ i ) ……有待学习。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=10000;
int k;
ll a[210],b[110],n;
ll tmp[210],s[110],ss[110];

void mult(ll *A,ll *B,ll *C)
{
    memset(tmp,0,sizeof(tmp));
    for(int i=0;i<k;i++)
        for(int j=0;j<k;j++)
            tmp[i+j]=(tmp[i+j]+A[i]*B[j])%mod;
    for(int i=2*k-2;i>=k;i--)
    {
        for(int j=1;j<=k;j++)
            tmp[i-j]=(tmp[i-j]+tmp[i]*b[j])%mod;
    }
    for(int i=0;i<k;i++)
        C[i]=tmp[i];
}

void solve(ll n)
{
    int nowx=1;
    memset(s,0,sizeof(s));
    s[0]=1;
    memset(ss,0,sizeof(ss));
    if (k>1) ss[1]=1;
    else ss[0]=b[1];

    n=n-k+1;
    while(n)
    {
        if (n&1) mult(s,ss,s);
        n>>=1;nowx<<=1; 
        if (nowx<k)
        {
            for(int i=0;i<k;i++)
                ss[i]=(i==nowx);
        }
        else mult(ss,ss,ss);
    }

    for(int i=k;i<=2*k-2;i++)
    {
        a[i]=0;
        for(int j=1;j<=k;j++)
            a[i]=(a[i]+a[i-j]*b[j])%mod;
    }
    ll ans=0;
    for(int i=0;i<k;i++)
        ans=(ans+a[i+k-1]*s[i])%mod;
    printf("%lld\n",ans);
}

int main()
{
    while(scanf("%d",&k)&&k)
    {
        for(int i=0;i<k;i++)
            scanf("%lld",&a[i]);
        for(int i=1;i<=k;i++)
            scanf("%lld",&b[i]);
        scanf("%lld",&n);

        if (n<k) printf("%lld\n",a[n]);
        else solve(n);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值