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

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

#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;
}
posted @ 2018-07-04 17:53  Maxwei_wzj  阅读(154)  评论(0编辑  收藏  举报