LG4723 【模板】常系数线性递推
P4723 【模板】常系数齐次线性递推
题目描述
求一个满足$k$阶齐次线性递推数列${a_i}$的第$n$项。
即:$a_n=\sum\limits_{i=1}^{k}f_i \times a_{n-i}$
输入输出格式
输入格式:
第一行两个数$n$,$k$,如题面所述。
第二行$k$个数,表示$f_1 \ f_2 \ \cdots \ f_k$
第三行$k$个数,表示$a_0 \ a_1 \ \cdots \ a_{k-1}$
输出格式:
一个数,表示 $a_n \% 998244353$ 的值
输入输出样例
说明
$N = 10^{9} , K = 32000$
题解
先修《数学选修4-2:矩阵与变换》。
常系数线性递推
给出递推式\(f_n=\sum_{i=1}^ka_if_{n-i}\),和初值条件\(f_1,f_2,\dots ,f_k\),求\(f_n\)。
可以用生成函数解一下,然后多项式求逆,\(O(n\log n)\)。当然这对于\(n=10^9\)的数据范围是不行的。
矩阵快速幂解法
由递推式,构造矩阵和列向量
0 & 1 & 0 & \dots & 0\\
0 & 0 & 1 & \dots & 0\\
0 & 0 & 0 & \dots & 0\\
\vdots & \vdots & \vdots & \ddots & \vdots\\
0 & 0 & 0 & \dots & 1\\
a_k & a_{k-1} & a_{k-2} & \dots & a_1
\end{bmatrix}
,F=\begin{bmatrix}
f_1\\
f_2\\
f_3\\
\vdots\\
f_k
\end{bmatrix}
\]
计算\(A^{n-k}F\)即可,\(O(k^3\log n)\)。然而这对于\(k=32000\)的数据范围还是不行。
矩阵的特征值和特征向量
\(A^nF\)是线性变换的形式,自然也可以用特征值与特征向量来求解。
特征多项式是\(f_A(\lambda)=|A-\lambda I|\),于是特征方程为
-\lambda & 1 & 0 & \dots & 0\\
0 & -\lambda & 1 & \dots & 0\\
\vdots & \vdots & \vdots & \ddots & \vdots\\
0 & 0 & 0 & \dots & 1\\
a_k & a_{k-1} & a_{k-2} & \dots & a_1-\lambda
\end{bmatrix}=0
\]
手动高斯消元,消成上三角可得
a_k+a_{k-1}\lambda+a_{k-2}\lambda^2+\dots+a_1\lambda^{k-1}=\lambda^k
\]
然后呢,把特征值解出来然后用特征向量那套理论吗?虽然这种方法可行,但是只能做低次的(4次及以下),所以我们需要新科技。
矩阵的多项式
对于\(n\)次多项式\(f(x)\),将矩阵\(A\)看做自变量带入,得
\]
记\(f(A)\)为\(A\)的\(n\)次多项式。与另一个\(A\)的\(m\)次多项式\(g(A)\),其乘法运算满足交换律,即
\]
Cayley-Hamilton 定理
特征多项式\(f_A(x)=\sum_{i=0}^{k-1}a_{k-i}x^i-x^k\),这里注意下标。
定理:\(f_A(A)=0\),即矩阵被自己的特征多项式化零。记忆方法:\(f_A(A)=|A-AI|=0\)。
推论:\(A^n=q(A)f_A(A)+r(A)=r(A)\),其中\(r(A)=A^n\mod f_A(A)\)。
所以\(A^nF=r(A)F=\sum_{i=0}^{k-1}r_iA^iF\),\(O(k^4)\)。???
多项式优化
将Cayley-Hamilton定理的推论变成普通多项式:\(x^n=q(x)f_A(x)+r(x)\),其中\(r(x)=x^n\mod f_A(x)\)。
于是我们可以对多项式\(x\)做快速幂并取模\(f_A(x)\),即可得出\(r(x)\)的系数,即\(r(A)\)的系数,\(O(k\log k\log n)\)。
而\(A^iF=\begin{bmatrix}f_{1+i} & f_{2+i} & \dots & f_{k+i}\end{bmatrix}^T\),所以\(f_{n+k}=\sum_{i=0}^{k-1}r_if_{k+i}\)。
问题转化成了如何求\(f\)的前\(2k\)项。这时使用生成函数和多项式求逆,\(O(k\log k)\)。
于是我们便得到了\(O(k\log k\log n+k\log k+k)\)的优秀解法。
小优化
刚才说的快速幂是指\(A^{n-k}\),如果我们做到\(A^{n-1}\),就只需要前\(k\)项了,那么就用不着什么生成函数了。\(O(k\log k\log n+k)\)。
UPD:然后我发现\(a_i\)系数要翻转,还要取相反数,是我把特征方程写反了?的确数学书上的应该是\(|\lambda I-A|\),但这没有影响。我看差异是我和其他人转移矩阵列的不一样,我把他们的做了一个中心对称(这是讲义的风格),然后特征方程都不一样了?我觉得这不是我以现有的水平能搞明白的问题。但是大众的写法的优势在于他们的最高次项系数为1,在做暴力多项式取模的时候很好办。
UPD:最近我发现最开始的那个矩阵含有 \(a\) 的最后一行写反了……
void num_trans(polynomial&a,int inverse){
int limit=a.size(),len=log2(limit);
static vector<int> bit_rev;
if(bit_rev.size()!=limit){
bit_rev.resize(limit);
for(int i=0;i<limit;++i) bit_rev[i]=bit_rev[i>>1]>>1|(i&1)<<(len-1);
}
for(int i=0;i<limit;++i)if(i<bit_rev[i]) swap(a[i],a[bit_rev[i]]);
for(int step=1;step<limit;step<<=1){
int gn=fpow(inverse==1?g:g_inv,(mod-1)/(step<<1));
for(int even=0;even<limit;even+=step<<1){
int odd=even+step,gk=1;
for(int k=0;k<step;++k,gk=mul(gk,gn)){
int t=mul(gk,a[odd+k]);
a[odd+k]=add(a[even+k],mod-t),a[even+k]=add(a[even+k],t);
}
}
}
if(inverse==-1){
int lim_inv=fpow(limit,mod-2);
for(int i=0;i<limit;++i) a[i]=mul(a[i],lim_inv);
}
}
polynomial poly_inv(polynomial a,int n){ // mod x^n
polynomial b(1,fpow(a[0],mod-2));
if(n==1) return b;
int limit;
for(limit=2;limit<n;limit<<=1){
polynomial a1(a.begin(),a.begin()+limit);
a1.resize(limit<<1),num_trans(a1,1);
b.resize(limit<<1),num_trans(b,1);
for(int i=0;i<limit<<1;++i) b[i]=mul(add(2,mod-mul(a1[i],b[i])),b[i]);
num_trans(b,-1),b.resize(limit);
}
a.resize(limit<<1),num_trans(a,1);
b.resize(limit<<1),num_trans(b,1);
for(int i=0;i<limit<<1;++i) b[i]=mul(add(2,mod-mul(a[i],b[i])),b[i]);
num_trans(b,-1),b.resize(n);
return b;
}
polynomial poly_div(polynomial f,polynomial g){ // return the quotient
int n=f.size()-1,m=g.size()-1;
reverse(g.begin(),g.end()),g.resize(n-m+1),g=poly_inv(g,n-m+1);
reverse(f.begin(),f.end()),f.resize(n-m+1);
int limit=1<<int(ceil(log2(2*(n-m)+1)));
f.resize(limit),g.resize(limit);
num_trans(f,1),num_trans(g,1);
for(int i=0;i<limit;++i) f[i]=mul(f[i],g[i]);
num_trans(f,-1),f.resize(n-m+1);
return reverse(f.begin(),f.end()),f;
}
polynomial poly_mod(polynomial f,polynomial g){ // return the reminder
int n=f.size()-1,m=g.size()-1;
polynomial q=poly_div(f,g);
int limit=1<<int(ceil(log2(n+1)));
g.resize(limit),q.resize(limit);
num_trans(g,1),num_trans(q,1);
for(int i=0;i<limit;++i) g[i]=mul(g[i],q[i]);
num_trans(g,-1);
for(int i=0;i<m;++i) f[i]=add(f[i],mod-g[i]);
return f.resize(m),f;
}
int n,k;
void mul(polynomial&a,polynomial b,co polynomial&p){
static co int limit=1<<int(ceil(log2(2*k-1)));
a.resize(limit),b.resize(limit);
num_trans(a,1),num_trans(b,1);
for(int i=0;i<limit;++i) a[i]=mul(a[i],b[i]);
num_trans(a,-1),a.resize(2*k-1);
a=poly_mod(a,p);
}
int main(){
read(n),read(k);
polynomial a(k),f(k);
for(int i=1;i<=k;++i) a[k-i]=(mod-read<int>()%mod)%mod; // [-1e9,1e9]
for(int i=0;i<k;++i) f[i]=(read<int>()%mod+mod)%mod;
if(n<k) return printf("%d\n",f[n]),0;
a.push_back(1);
polynomial rmd(1,1),tmp(2);tmp[1]=1;
for(;n;n>>=1,mul(tmp,tmp,a))
if(n&1) mul(rmd,tmp,a);
int ans=0;
for(int i=0;i<k;++i) ans=add(ans,mul(rmd[i],f[i]));
printf("%d\n",ans);
return 0;
}
LG4723 【模板】常系数线性递推的更多相关文章
- 【BZOJ4944】【NOI2017】泳池 概率DP 常系数线性递推 特征多项式 多项式取模
题目大意 有一个\(1001\times n\)的的网格,每个格子有\(q\)的概率是安全的,\(1-q\)的概率是危险的. 定义一个矩形是合法的当且仅当: 这个矩形中每个格子都是安全的 必须紧贴网格 ...
- 51nod1538:一道难题(常系数线性递推/Cayley-Hamilton定理)
传送门 Sol 考虑要求的东西的组合意义,问题转化为: 有 \(n\) 种小球,每种的大小为 \(a_i\),求选出大小总和为 \(m\) 的小球排成一排的排列数 有递推 \(f_i=\sum_{j= ...
- Cayley-Hamilton定理与矩阵快速幂优化、常系数线性递推优化
原文链接www.cnblogs.com/zhouzhendong/p/Cayley-Hamilton.html Cayley-Hamilton定理与矩阵快速幂优化.常系数线性递推优化 引入 在开始本文 ...
- 【XSY2730】Ball 多项式exp 多项式ln 多项式开根 常系数线性递推 DP
题目大意 一行有\(n\)个球,现在将这些球分成\(k\) 组,每组可以有一个球或相邻两个球.一个球只能在至多一个组中(可以不在任何组中).求对于\(1\leq k\leq m\)的所有\(k\)分别 ...
- 常系数线性递推的第n项及前n项和 (Fibonacci数列,矩阵)
(一)Fibonacci数列f[n]=f[n-1]+f[n-2],f[1]=f[2]=1的第n项的快速求法(不考虑高精度). 解法: 考虑1×2的矩阵[f[n-2],f[n-1]].根据fibon ...
- 模板 - 线性递推BM
模数是998244353的话好像NTT可以更快. #include<bits/stdc++.h> using namespace std; typedef long long ll; co ...
- [模板]线性递推+BM
暴力版本: #include<bits/stdc++.h> #define mod 998244353 using namespace std; typedef long long int ...
- CF1106F Lunar New Year and a Recursive Sequence 线性递推 + k次剩余
已知\(f_i = \prod \limits_{j = 1}^k f_{i - j}^{b_j}\;mod\;998244353\),并且\(f_1, f_2, ..., f_{k - 1} = 1 ...
- Codeforces 1106F Lunar New Year and a Recursive Sequence (数学、线性代数、线性递推、数论、BSGS、扩展欧几里得算法)
哎呀大水题..我写了一个多小时..好没救啊.. 数论板子X合一? 注意: 本文中变量名称区分大小写. 题意: 给一个\(n\)阶递推序列\(f_k=\prod^{n}_{i=1} f_{k-i}b_i ...
随机推荐
- K8S从入门到放弃系列-(15)Kubernetes集群Ingress部署
Ingress是kubernetes集群对外提供服务的一种方式.ingress部署相对比较简单,官方把相关资源配置文件,都已经集合到一个yml文件中(mandatory.yaml),镜像地址也修改为q ...
- Java开发笔记(一百三十一)Swing的列表框
前面介绍了选择框的用法,当时为了方便用户勾勾点点,无论是复选框还是单选按钮,统统把所有选项都摆在界面上.倘若只有两三个选项还好办,要是选项数量变多比如超过五个,这么多的选择框一齐在界面罗列,不光程序员 ...
- python 之网络编程(基于TCP协议Socket通信的粘包问题及解决)
8.4 粘包问题 粘包问题发生的原因: 1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包),这样接收端,就难于分辨出来了,必须提供科学的拆包机制. ...
- git及gitflow命令备忘
全文xxx表示你的分支名 一.git 删除本地分支 git branch -d xxx 删除远程分支 git push origin --delete xxx 查看所有分支 本地分支 git bran ...
- quartz2.3.0系列目录——带您由浅入深全面掌握quartz2.3.0
quartz2.3.0系列目录 官网下载地址:http://www.quartz-scheduler.org/downloads/ 本系列demo全部来源于官网,仅仅是简化和汉化了注释!一部分代码de ...
- 「PKUWC2018/PKUSC2018」试题选做
「PKUWC2018/PKUSC2018」试题选做 最近还没想好报THUSC还是PKUSC,THU发我的三类约(再来一瓶)不知道要不要用,甚至不知道营还办不办,协议还有没有用.所以这些事情就暂时先不管 ...
- 转 js一个简单实用的弹出层
关闭 点击查看 >> <html> <head> <title>新文件标题</title> <script type=" ...
- ubuntu ufw 配置
ubuntu ufw 配置 Ubuntu 18.04 LTS 系统中已经默认附带了 UFW 工具,如果您的系统中没有安装,可以在「终端」中执行如下命令进行安装: 1 sudo apt install ...
- atan、atanf、atanl、atan2、atan2f、atan2l
很久不发博客了,今天在园中计算各种角,于是复习下fan正切函数 计算x的反正切值 (atan.atanf和 atanl) 或y/x 的反正切值 (atan2.atan2f和 atan2l). ...
- 前端以及django零碎补充
前端 1. js的循环each 前端需要循环,可以借助each这个方法 var dic = {'key1':'value1','key2':'value2'}; ']; //each(变量, 方法) ...