bzoj 3231 & 洛谷 2461 题解(Flag=1)

博客介绍了使用矩阵快速幂解决bzoj 3231和洛谷 2461题目,涉及数列递推和前缀和的计算。通过维护一个1×(k+1)的矩阵,利用矩阵乘法求解a数组从m到n的和模p。详细解释了初始矩阵和转移矩阵的构造,并提供了代码实现。

题意简述

(纯代数)定义数列ai=a_i=ai=
{bi                (i<=k)∑j=1kai−jcj    (i>k)(bi,ci<=109) \begin{cases} b_i\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (i<=k)\\ \\ \\ \sum\limits_{j=1}^{k} a_{i-j}c_j\ \ \ \ (i>k) \end{cases}\\ (b_i,c_i<=10^{9}) bi                (i<=k)j=1kaijcj    (i>k)(bi,ci<=109)
aaa数组从mmmn(m,n<=1018)n(m,n<=10^{18})n(m,n<=1018)的和模p(p<=108)p(p<=10^8)p(p<=108)的值,也就是∑i=mnai%p\sum\limits_{i=m}^{n}a_i\%pi=mnai%p

数据

输入
2
1 1
1 1
2 10 1000003
输出
142

思路

一个朴素的思路:暴力递推。
发现n,mn,mn,m的数据是101810^{18}1018,显然会TLETLETLE
所以需要一个很快的算法,要么是矩阵快速幂,要么是玄学的O(1)O(1)O(1)公式。明显,kkk很大(会到151515),所以公式是没有的。需要矩阵快速幂的做法。
(矩阵快速幂目前还没写博客,留一个FlagFlagFlag在这里)
现在设Calc(x)Calc(x)Calc(x)为求a1a_1a1axa_xax的和,那么答案=Calc(n)−Calc(m−1)Calc(n)-Calc(m-1)Calc(n)Calc(m1)(以下省略取模)。
考虑如何求Calc(x)Calc(x)Calc(x)
我们要求的是一段前缀和,而不是具体一项。
wdnmd这怎么求???
想想发现,我们珂以在矩阵里面顺便维护上和啊!!!

那么我们的矩阵就是一个1×(k+1)1\times (k+1)1×(k+1)的矩阵,前面kkk个是当前的aaa值,第k+1k+1k+1个是和。然后每次乘一个(k+1)×(k+1)(k+1)\times (k+1)(k+1)×(k+1)的矩阵转移,最后取第一行第k+1k+1k+1个,就能求出Calc(x)Calc(x)Calc(x)了。

剩下的部分分为222步。

Step1.Step1.Step1.求初始矩阵

初始矩阵就是x=1x=1x=1时的情况,也就是
[b1b2⋯∑i=1kbi] \begin{bmatrix} b_1 & b_2\cdots & \sum\limits_{i=1}^{k}b_i \end{bmatrix} [b1b2i=1kbi]
为了方便表述,设sssbbb的前缀和,即sx=∑i=1xbis_x=\sum\limits_{i=1}^{x}b_isx=i=1xbi
那么初始矩阵就是
[b1b2⋯sk] \begin{bmatrix} b_1 & b_2\cdots & s_k \end{bmatrix} [b1b2sk]
在代码中,这个矩阵的名字叫InitialInitialInitial
Step1 SolvedStep1\ SolvedStep1 Solved

Step2.Step2.Step2.求转移矩阵
我们要求一个转移矩阵TransitTransitTransit使得:
[b1b2⋯sk]×Transit=[b2b3⋯sk+1] \begin{bmatrix} b_1 & b_2\cdots & s_k \end{bmatrix}\times Transit=\\ \begin{bmatrix} b_2 & b_3\cdots & s_{k+1} \end{bmatrix} [b1b2sk]×Transit=[b2b3sk+1]
考虑到矩阵乘法的规则,我们会发现:
∑i=1kbiTransit[i][1]+skTransit[k+1][1]=b2([1])∑i=1kbiTransit[i][2]+skTransit[k+1][2]=b3([2])⋯∑i=1kbiTransit[i][k−1]+skTransit[k+1][k−1]=bk([k−1])∑i=1kbiTransit[i][k]+skTransit[k+1][k]=bk+1([k])∑i=1kbiTransit[i][k+1]+skTransit[k+1]k+1]=sk+1([k+1]) \sum\limits_{i=1}^{k}b_iTransit[i][1]+s_kTransit[k+1][1]=b_2 (^{[1]})\\ \sum\limits_{i=1}^{k}b_iTransit[i][2]+s_kTransit[k+1][2]=b_3 (^{[2]})\\ \cdots\\ \sum\limits_{i=1}^{k}b_iTransit[i][k-1]+s_kTransit[k+1][k-1]=b_k (^{[k-1]})\\ \sum\limits_{i=1}^{k}b_iTransit[i][k]+s_kTransit[k+1][k]=b_{k+1} (^{[k]})\\ \sum\limits_{i=1}^{k}b_iTransit[i][k+1]+s_kTransit[k+1]k+1]=s_{k+1} (^{[k+1]})\\ i=1kbiTransit[i][1]+skTransit[k+1][1]=b2([1])i=1kbiTransit[i][2]+skTransit[k+1][2]=b3([2])i=1kbiTransit[i][k1]+skTransit[k+1][k1]=bk([k1])i=1kbiTransit[i][k]+skTransit[k+1][k]=bk+1([k])i=1kbiTransit[i][k+1]+skTransit[k+1]k+1]=sk+1([k+1])
(后面括号里的是式子编号,和式子本身无关)
对于式子([1])(^{[1]})([1])到式子([k−1])(^{[k-1]})([k1]),因为b2,b3⋯bk)b_2,b_3\cdots b_k)b2,b3bk)都是两个矩阵里面共有的部分,只要让Transit[i][i−1]=1Transit[i][i-1]=1Transit[i][i1]=1,其余Transit[i]Transit[i]Transit[i]的值均为000即可。
对于式子([k])(^{[k]})([k]),根据aaa的递推式,只要让Transit[][k]Transit[][k]Transit[][k](即第k列)=c1,c2⋯ck,0=c_1,c_2\cdots c_k,0=c1,c2ck,0即可。
对于式子([k+1])(^{[k+1]})([k+1]),根据sss的定义,我们知道sk+1=sk+ak+1s_{k+1}=s{k}+a{k+1}sk+1=sk+ak+1,其中sks_ksk是上一个矩阵中的一项,ak+1a_{k+1}ak+1的表示法刚刚已经写过了,所以这个Transit[][k+1]Transit[][k+1]Transit[][k+1](即第k+1列)=c1,c2⋯ck,1=c_1,c_2\cdots c_k,1=c1,c2ck,1。在代码中,把这个数组的名字简写为TransTransTrans

如果这一大串字看不懂,举个栗子:
k=4k=4k=4
Transit=[000c4c4100c3c3010c2c2001c1c100001] Transit=\\ \begin{bmatrix} 0 & 0 & 0 & c_4 & c_4 \\ 1 & 0 & 0 & c_3 & c_3 \\ 0 & 1 & 0 & c_2 & c_2 \\ 0 & 0 & 1 & c_1 & c_1 \\ 0 & 0 & 0 & 0 & 1 \\ \end{bmatrix} Transit=010000010000010c4c3c2c10c4c3c2c11
备注:如果这个都看不懂,您珂以选择:
1.去请教同学/老师/同班巨佬
2.手动模拟一遍,把k=4k=4k=4带入,然后把上面那个TransitTransitTransit矩阵和InitialInitialInitial矩阵手动乘一遍,就明白了
3.意会

代码:

#include<bits/stdc++.h>
#define mod p
#define K 20
#define int long long
using namespace std;
int n,m,p;

struct mat//封装结构体
{
    int m[K][K];//矩阵
    int* operator[](int i)//封装取下标运算符
    {
        return m[i];
    }

    mat(int x)//初始化矩阵全部为x
    {
        for(int i=0;i<K;i++)
        {
            for(int j=0;j<K;j++)
            {
                m[i][j]=x;
            }
        }
    }
    void Set(int x=0)//方便初始化之后全部设置为x(这个里面没有用到)
    {
        for(int i=0;i<K;i++)
        {
            for(int j=0;j<K;j++)
            {
                m[i][j]=x;
            }
        }
    }
    void Identity()//单位矩阵
    {
        Set(0);
        for(int i=0;i<K;i++)
        {
            m[i][i]=1;
        }
    }
};
mat operator*(mat x,mat y)//封装乘法
{
    mat ans(0);

    for(int i=1;i<K;i++)
    {
        for(int j=1;j<K;j++)
        {
            ans[i][j]=0;
            for(int k=1;k<K;k++)
            {
                ans[i][j]+=x[i][k]*y[k][j];
                ans[i][j]%=mod;//取模。。。
            }
        }
    }
    return ans;
}
mat operator^(mat x,int p)//封装快速幂
{
    mat ans(0);ans.Identity();//初始为单位矩阵
    while(p)
    {
        if (p&1) ans=ans*x;
        x=x*x,p>>=1;
    }//和快速幂差不多
    return ans;
}

int k;
int b[K],c[K];
int s[K];
void Input()
{
    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++)
    {
        s[i]=(s[i-1]+b[i])%mod;//处理前缀和
        b[i]%=mod;
        c[i]%=mod;
    }
}

int Calc(int x)
{
    if (x<=k)//这个很显然
    {
        return s[x];
    }
    else
    {
        mat Initial(0);//Initial矩阵
        for(int i=1;i<=k;i++) Initial[1][i]=b[i];
        Initial[1][k+1]=s[k];

        mat Trans(0);//Trans矩阵
        for(int i=1;i<=k;i++)
        {
            Trans[i][k]=Trans[i][k+1]=c[k-i+1];
        }
        for(int i=2;i<=k;i++)
        {
            Trans[i][i-1]=1;
        }
        Trans[k+1][k+1]=1;//构造方法见上面

        mat ans=Initial*(Trans^(x-k));
        return ans[1][k+1];
    }
}

int Q[100];
main()
{
    Input();
    printf("%lld\n",(Calc(n)%mod-Calc(m-1)%mod+mod)%mod);
    //这边取绝对模,要先膜,再加,再膜
    return 0;
}

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值