题意简述
(纯代数)定义数列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=1∑kai−jcj (i>k)(bi,ci<=109)
求aaa数组从mmm到n(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=m∑nai%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_1a1到axa_xax的和,那么答案=Calc(n)−Calc(m−1)Calc(n)-Calc(m-1)Calc(n)−Calc(m−1)(以下省略取模)。
考虑如何求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}
[b1b2⋯i=1∑kbi]
为了方便表述,设sss为bbb的前缀和,即sx=∑i=1xbis_x=\sum\limits_{i=1}^{x}b_isx=i=1∑xbi。
那么初始矩阵就是
[b1b2⋯sk]
\begin{bmatrix}
b_1 & b_2\cdots & s_k
\end{bmatrix}
[b1b2⋯sk]
在代码中,这个矩阵的名字叫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}
[b1b2⋯sk]×Transit=[b2b3⋯sk+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=1∑kbiTransit[i][1]+skTransit[k+1][1]=b2([1])i=1∑kbiTransit[i][2]+skTransit[k+1][2]=b3([2])⋯i=1∑kbiTransit[i][k−1]+skTransit[k+1][k−1]=bk([k−1])i=1∑kbiTransit[i][k]+skTransit[k+1][k]=bk+1([k])i=1∑kbiTransit[i][k+1]+skTransit[k+1]k+1]=sk+1([k+1])
(后面括号里的是式子编号,和式子本身无关)
对于式子([1])(^{[1]})([1])到式子([k−1])(^{[k-1]})([k−1]),因为b2,b3⋯bk)b_2,b_3\cdots b_k)b2,b3⋯bk)都是两个矩阵里面共有的部分,只要让Transit[i][i−1]=1Transit[i][i-1]=1Transit[i][i−1]=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,c2⋯ck,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,c2⋯ck,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;
}

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





