给定数列前k项\(h_0...h_{k-1}\),其后的项满足:\(h_i=\sum_{i=1}^kh_{i-j}a_i\),其中\(a_1...a_k\)是给定的系数,求\(h_n\)

  

  

  

数据范围小的时候:

​  做法一:暴力\(O(nk)\)的DP

​  做法二:矩阵快速幂.

      记\(H_i=\begin{bmatrix}h_i&h_{i+1}&...&h_{i+k-1}\end{bmatrix}\). 则\(h_n\)是\(H_{n-k+1}\)的最后一项。

      \(H_{n-k+1}=H_0M^{n-k+1}\)

      其中\(M\)是转移矩阵,如当\(k=4\)时是这么填的:

\[M=\begin{bmatrix}
0&0&0&a_4\\
1&0&0&a_3\\
0&1&0&a_2\\
0&0&1&a_1
\end{bmatrix}
\]

      时间复杂度\(O(k^3lg n)\)

  

  

  

数据范围大一些的时候:

  

  \(k\leq2000,n\leq10^9\). 这时候矩阵快速幂也做不了了

  

  还是拿\(k=4\)时举例,\(M\)的特征多项式\(f(\lambda)\)为:

\[f(\lambda)=det(\lambda I-M)=\begin{bmatrix}
\lambda&0&0&0\\
0&\lambda&0&0\\
0&0&\lambda&0\\
0&0&0&\lambda
\end{bmatrix}
-\begin{bmatrix}
0&0&0&a_4\\
1&0&0&a_3\\
0&1&0&a_2\\
0&0&1&a_1
\end{bmatrix}=\begin{bmatrix}
\lambda&0&0&-a_4\\
-1&\lambda&0&-a_3\\
0&-1&\lambda&-a_2\\
0&0&-1&\lambda-a_1
\end{bmatrix}
\]

  用行列式的性质,将\(f(\lambda)\)按最后一列拉普拉斯展开,得到如下,其中\((-1)^{i+j}f(x)_{i,j}\)即行列式定义里的代数余子式:

\[\begin{aligned}
f(\lambda)&=\sum_{i=1}^ka_{k-i+1}(-1)^{i+j}f(\lambda)_{i,j} &取j=k(按最后一列展开)\\
&=\sum_{i=1}^ka_{k-i+1}(-1)^{i+k}f(\lambda)_{i,k}
\end{aligned}
\]

  化简得到如下式子(也可以按\(k=4\)带进去看看规律)

\[f(\lambda)=\lambda^k-\sum_{i=1}^ka_i\lambda^{k-i}
\]

  

  现在明确一个定义,\(f(x)\)这个函数的自变量\(x\)可以是实数,也可以是矩阵等等。这个函数仅仅是表示如何将自变量组合起来。表达的意思也会多样化,比如多项式、矩阵的多项式...下文会随时切换自变量的种类,但是函数的本质不变。

  

  \(\lambda\)是\(M\)的特征值,是一个数。但是根据Cayley-Hamilton定理,如果把\(\lambda\)替换成\(M\)代入得到\(f(M)=M^k-\sum_{i=1}^ka_iM^{k-i}\),结果为一个零矩阵,即\(M^k-\sum_{i=1}^ka_iM^{k-i}=0\)

  

  

  我们想要求\(M\)的\(n\)次方(这里的\(n\)只是代表\(M\)的\(n\)次方,题目中\(n\)应该用\(n-k+1\)替代),然而\(M^n\)直接快速幂求不现实,复杂度为\(O(k^3lg n)\).

  首先退一步考虑,要求一个数字的n次方\(x^n\),如果我们把\(x^n\)对\(f(x)\)取模会发生什么?

​  根据多项式取模的定义,\(x^n \;\text{mod}\; f(x)=f(x)g(x)+r(x)\),其中\(g(x)\)和\(r(x)\)是两个多项式.

​  将\(x\)看成\(M\),那么\(f(M)\)为0.

  故\(M^n \;\text{mod}\; f(M)=r(M)\),且\(M^n=M^n \;\text{mod}\; f(M)\),那么\(M_n=r(M)\)这个多项式

​  根据多项式取模的特性,\(r(x)\)的次数严格小于模数\(f(x)\)的次数\(k\). 那么\(r(x)\)所包含的\(M\)的指数一定小于\(k\),到达了可以计算的范围。

  要求\(M^n\),就只需要求\(M^n \;\text{mod}\; f(M)\)的多项式\(r(M)\)。如果两个多项式\(A(x)\)和\(B(x)\)对模数取模分别得到\(C(x)\)和\(D(x)\),那么多项式\(A(x)B(x)\)对模数取模结果就是\(C(x)D(x)\)。

​  那么就可以用快速幂来求解\(M^n \;\text{mod}\; f(M)\)的结果了,也就是求出了\(r(x)\)的各项系数(记为\(c_i\))。实际计算中,表面上是在计算\(M^n\),实际上计算的是\(M^n \;\text{mod}\; f(M)\)的结果。

  

  

  

  至此求出\(r(x)=\sum\limits_{i=0}^{k-1}c_ix^i\). 将它看成矩阵的多项式代入\(M\),得\(r(M)=\sum\limits_{i=0}^{k-1}c_iM^i\)

​  所以\(M^n=\sum\limits_{i=0}^{k-1}c_iM^i\)

​  把\(n\)替换成题目所需要的\(n-k+1\),最终答案\(h_n\)为\(H_0M^{n-k+1}\)的最后一项。

\[H_0M^{n-k+1}=H_0\sum_{i=0}^{k-1}c_iM^i=\sum_{i=0}^{k-1}c_iH_0M_i=\sum_{i=0}^{k-1}c_iH_i
\]

  那么要求的是\(H_0M^{n-k+1}\)的最后一项。记\(last(H_i)=h_{k+i}\) ,那么

\[h_n=last(H_0M^{n-k+1})=\sum_{i=0}^{k-1}c_ilast(H_i)=\sum_{i=0}^{k-1}c_ih_{i+k}
\]

  发现\(i+k\in[k,2k-1]\),所以暴力算出\(h_k...h_{2k-1}\),代入求解得到\(h_n\),至此全部求完。

  分析复杂度:多项式乘法此处用暴力算会比FFT快,耗时最多的集快速幂求\(r(x)\) ,复杂度为\(O(k^2lgn)\)。

#include <cstdio>
using namespace std;
const int K=4005,mod=1e9+7;
int n,k;
int a[K],h[K];
int b[K],c[K],t[K],mo[K];
inline void add(int &x,int y){
x+=y;
if(x>=mod) x-=mod;
}
void mul(int *x,int *y,int *z){
for(int i=0;i<=2*k-2;i++) t[i]=0;
for(int i=0;i<k;i++)
for(int j=0;j<k;j++)
add(t[i+j],1LL*x[i]*y[j]%mod);
for(int i=2*k-2;i>=k;i--){
for(int j=k-1;j>=0;j--)
add(t[i-k+j],mod-1LL*t[i]*mo[j]%mod);
t[i]=0;
}
for(int i=0;i<k;i++) z[i]=t[i];
}
void ksm(int y){
for(;y;mul(b,b,b),y>>=1)
if(y&1)
mul(c,b,c);
}
int main(){
freopen("input.in","r",stdin);
scanf("%d%d",&n,&k); n++;
for(int i=1;i<=k;i++){
scanf("%d",&a[i]);
if(a[i]<0) a[i]+=mod;
}
for(int i=1;i<=k;i++){
scanf("%d",&h[i]);
if(h[i]<0) h[i]+=mod;
}
mo[k]=1;
for(int i=1;i<=k;i++) mo[k-i]=mod-a[i];
if(n<=k){printf("%d\n",h[n]);return 0;}
b[1]=1; c[0]=1;
ksm(n-k);
for(int i=k+1;i<=2*k;i++)
for(int j=1;j<=k;j++)
add(h[i],1LL*a[j]*h[i-j]%mod);
int ans=0;
for(int i=0;i<k;i++)
add(ans,1LL*c[i]*h[i+k]%mod);
printf("%d\n",ans);
return 0;
}

  

  

EXT

    

  如果\(k\)也比较大,那么要上多项式全家桶来优化多项式计算了!复杂度\(O(k\log k\log n)\)

  

  来啊

   

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
const int K=200005,mod=998244353,G=3;
int n,k,a[K],h[K];
inline void swap(int &x,int &y){int t=x;x=y;y=t;}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}
inline void add(int &x,int y){
y=(y%mod+mod)%mod;
(x+=y)%=mod;
}
inline int pow(int x,int y){
int ret=1;
for(;y;x=1LL*x*x%mod,y>>=1)
if(y&1) ret=1LL*ret*x%mod;
return ret;
}
namespace NTT{/*{{{*/
int n,invn,bit,rev[K*4],A[K*4],B[K*4],W[K*4][2];
void build(){
int bas=pow(G,mod-2);
for(int i=0;i<=18;i++){
W[1<<i][0]=pow(G,(mod-1)/(1<<i));
W[1<<i][1]=pow(bas,(mod-1)/(1<<i));
}
}
void init(int na,int nb,vi &a,vi &b,int fn=0){
if(!fn) fn=na+nb;
for(n=1,bit=0;n<fn;n<<=1,bit++);
invn=pow(n,mod-2);
for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
for(int i=0;i<n;i++) A[i]=B[i]=0;
for(int i=0;i<na;i++) A[i]=a[i];
for(int i=0;i<nb;i++) B[i]=b[i];
}
void ntt(int *a,int f){
for(int i=0;i<n;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
int w_n,w,u,v;
for(int i=2;i<=n;i<<=1){
w_n=W[i][f==-1];
for(int j=0;j<n;j+=i){
w=1;
for(int k=0;k<i/2;k++){
u=a[j+k]; v=1LL*a[j+i/2+k]*w%mod;
a[j+k]=(u+v)%mod;
a[j+i/2+k]=(u+mod-v)%mod;
w=1LL*w*w_n%mod;
}
}
}
if(f==1) return;
for(int i=0;i<n;i++) a[i]=1LL*a[i]*invn%mod;
}
void calc(){
ntt(A,1);
ntt(B,1);
for(int i=0;i<n;i++) A[i]=1LL*A[i]*B[i]%mod;
ntt(A,-1);
}
void calchh(){
ntt(A,1);
ntt(B,1);
for(int i=0;i<n;i++) A[i]=(2LL*B[i]%mod+mod-1LL*A[i]*B[i]%mod*B[i]%mod)%mod;
ntt(A,-1);
}
}/*}}}*/
vi mop,b,c,T;
vi operator - (vi A,vi B){
int n=A.size(),m=B.size(),fn=max(n,m);
A.resize(fn);
for(int i=0;i<m;i++) add(A[i],-B[i]);
return A;
}
vi operator * (int a,vi A){
int n=A.size();
a=(a+mod)%mod;
for(int i=0;i<n;i++) A[i]=1LL*a*A[i]%mod;
return A;
}
vi operator * (vi &A,vi B){
int n=A.size(),m=B.size();
NTT::init(n,m,A,B);
NTT::calc();
A.resize(n+m-1);
for(int i=0;i<n+m-1;i++) A[i]=NTT::A[i];
return A;
}
vi inverse(vi A){
int n=A.size();
if(n==1){
A[0]=pow(A[0],mod-2);
return A;
}
vi B=A;
B.resize((n+1)/2);
B=inverse(B); int m=B.size();
NTT::init(n,m,A,B,n+m-1+m-1);
NTT::calchh();
B.resize(NTT::n);
for(int i=0;i<NTT::n;i++) B[i]=NTT::A[i]; //B=(2*B)-((A*B)*B);
B.resize(n);
return B;
}
vi operator / (vi A,vi B){
int n=A.size()-1,m=B.size()-1;
vi C;
if(n<m){
C.resize(1); C[0]=0;
return C;
}
reverse(A.begin(),A.end());
reverse(B.begin(),B.end());
B.resize(n-m+1);
C=A*inverse(B);
C.resize(n-m+1);
reverse(C.begin(),C.end());
return C;
}
void module(vi &A,vi B){
int n=A.size()-1,m=B.size()-1;
if(n<m) return;
vi D=A/B;
A=A-(B*D);
A.resize(m);
}
void ksm(int y){
for(;y;y>>=1){
if(y&1){
c=c*b;
module(c,mop);
}
b=b*b;
module(b,mop);
}
}
int main(){
freopen("input.in","r",stdin);
NTT::build();
scanf("%d%d",&n,&k); n++;
for(int i=1;i<=k;i++) scanf("%d",&h[i]),h[i]%=mod;
for(int i=1;i<=k;i++) scanf("%d",&a[i]),a[i]%=mod;
if(n<=k){printf("%d\n",h[n]);return 0;}
mop.resize(k+1);
mop[k]=1;
for(int i=1;i<=k;i++) mop[k-i]=(mod-a[i])%mod;
b.resize(2); b[1]=1;
c.resize(1); c[0]=1;
ksm(n-1);
int ans=0;
c.resize(k);
for(int i=0;i<k;i++)
add(ans,1LL*c[i]*h[i+1]%mod);
printf("%d\n",ans);
return 0;
}
参考资料

http://blog.csdn.net/qq_33229466/article/details/78933309 "ORZ"

【Learning】常系数线性齐次递推的更多相关文章

  1. LOJ 2304 「NOI2017」泳池——思路+DP+常系数线性齐次递推

    题目:https://loj.ac/problem/2304 看了各种题解…… \( dp[i][j] \) 表示有 i 列.第 j 行及以下默认合法,第 j+1 行至少有一个非法格子的概率,满足最大 ...

  2. bzoj 4161 Shlw loves matrixI【常系数线性齐次递推】

    并不会递推,不过板子挺好背的,只要是类似的递推都能用,但是注意c数组不能使负数 如果除了递推还有常数项的话,就用f[i]-f[i-1]的方式消掉常数项(然后多一个f[i-1]的项) #include& ...

  3. bzoj 4161 Shlw loves matrixI——常系数线性齐次递推

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4161 还是不能理解矩阵…… 关于不用矩阵理解的方法:https://blog.csdn.ne ...

  4. 模板-->常系数线性齐次递推(矩阵快速幂)

    如果有相应的OJ题目,欢迎同学们提供相应的链接 相关链接 所有模板的快速链接 Matrix模板 poj_2118_Firepersons,my_ac_code 简单的测试 None 代码模板 /* * ...

  5. 常系数齐次线性递推 & 拉格朗日插值

    常系数齐次线性递推 具体记在笔记本上了,以后可能补照片,这里稍微写一下,主要贴代码. 概述 形式: \[ h_n = a_1 h_{n-1}+a_2h_{n-2}+...+a_kh_{n-k} \] ...

  6. Re.常系数齐次递推

    前言 嗯   我之前的不知道多少天看这个的时候到底在干什么呢 为什么那么..  可能大佬们太强的缘故 最后仔细想想思路那么的emmm 不说了  要落泪了 唔唔唔 前置 多项式求逆 多项式除法/取模 常 ...

  7. 【Luogu4723】线性递推(常系数齐次线性递推)

    [Luogu4723]线性递推(常系数齐次线性递推) 题面 洛谷 题解 板子题QwQ,注意多项式除法那里每个多项式的系数,调了一天. #include<iostream> #include ...

  8. 【BZOJ4161】Shlw loves matrixI (常系数齐次线性递推)

    [BZOJ4161]Shlw loves matrixI (常系数齐次线性递推) 题面 BZOJ 题解 \(k\)很小,可以直接暴力多项式乘法和取模. 然后就是常系数齐次线性递推那套理论了,戳这里 # ...

  9. 【模板】BM + CH(线性递推式的求解,常系数齐次线性递推)

    这里所有的内容都将有关于一个线性递推: $f_{n} = \sum\limits_{i = 1}^{k} a_{i} * f_{n - i}$,其中$f_{0}, f_{1}, ... , f_{k ...

随机推荐

  1. 使用NNI的scikit-learn以及Tensorflow分析

    一.NNI简介 NNI (Neural Network Intelligence) 是自动机器学习(AutoML)的工具包. 它通过多种调优的算法来搜索最好的神经网络结构和(或)超参,并支持单机.本地 ...

  2. 【视频编解码·学习笔记】13. 提取PPS信息程序

    PPS结构解析 与之前解析SPS方式类似 一.定义PPS类: 在3.NAL Unit目录下,新建PicParamSet.cpp和PicParamSet.h,在这两个文件中写入类的定义和函数实现. 类定 ...

  3. 吴恩达(Andrew Ng)——机器学习笔记1

    之前经学长推荐,开始在B站上看Andrew Ng的机器学习课程.其实已经看了1/3了吧,今天把学习笔记补上吧. 吴恩达老师的Machine learning课程共有113节(B站上的版本https:/ ...

  4. 点斜杠 & 如何查看linux程序安装位置 dpkg -L yyy

    方法1: sudo find / -name ssh 方法2: Ubuntu下 看应用程序安装路径的方法 ubuntu下dpkg -L xxx看应用程序安装路径 1.点斜杠 “./”就代表在当前目录下 ...

  5. Python基础系列讲解——TCP协议的socket编程

    前言 我们知道TCP协议(Transmission Control Protocol, 传输控制协议)是一种面向连接的传输层通信协议,它能提供高可靠性通信,像HTTP/HTTPS等网络服务都采用TCP ...

  6. 解决maven update project 后项目jdk变成1.5

    http://blog.csdn.net/jay_1989/article/details/52687934

  7. LeetCode 303. Range Sum Query - Immutable (C++)

    题目: Given an integer array nums, find the sum of the elements between indices iand j (i ≤ j), inclus ...

  8. 实验二 Java面向对象程序设计 20135321

    课程:Java程序设计   班级:1353    姓名:余佳源  学号:20135321 成绩:             指导教师:娄嘉鹏      实验日期:2015-5-8 实验密级:       ...

  9. linux 常用命令-文件、文件夹管理

    1. 创建文件夹: mkdir dirName 删除文件夹: rm -rf * 删除当前目录下的所有文件以及文件夹(非交互式) rm -r  --recursive 递归式删除所删除目录以及子目录(有 ...

  10. PMS—团队展示

    点我查看作业原题 [队名] PMS(一群pm) [拟做的团队项目描述] 基于监控场景的视频摘要与人车检测跟踪系统 A system, under monitor scene, for video su ...