【模板】线性递推

本文介绍了一种利用矩阵快速幂和多项式取模解决特定递推数列问题的方法,通过构造特征多项式并应用Cayley-Hamilton定理,避免了直接计算高次幂矩阵,降低了算法复杂度。

fn,fn=∑i=1kaifn−if_n,f_n=\sum_{i=1}^ka_if_{n-i}fn,fn=i=1kaifni
k≤3e4,n≤1e9k\le 3e4,n\le 1e9k3e4,n1e9
首先暴力的矩阵乘是 k3lognk^3lognk3logn
引入概念:特征多项式
f(λ)=∣λI−A∣f(\lambda)=|\lambda I-A|f(λ)=λIA
III 为单位矩阵
然后对于这道题来讲,转移矩阵是这样的
λI−A=\lambda I-A=λIA=
λ−a1−a2...−ak−1−ak−1λ0...00−1λ...0...00...−1λ\begin{matrix} \lambda-a_1&-a_2&...&-a_{k-1}&-a_k \\-1&\lambda&0 &...&0 \\0&-1&\lambda&... &0\\& &...&\\0&0&...&-1&\lambda\end{matrix}λa1100a2λ10...0λ......ak1......1ak00λ
对第一行展开然后分别求行列式
f(λ)=(λ−a1)∗A1,1+(−a2)∗A1,2+...+(−ak)∗A1,kf(\lambda)=(\lambda-a_1)*A_{1,1}+(-a_2)*A_{1,2}+...+(-a_k)*A_{1,k}f(λ)=(λa1)A1,1+(a2)A1,2+...+(ak)A1,k
然后发现去除第一行和某一列后主对角线都是满的,消成下三角矩阵,行列式是主对角线的乘积
f(λ)=λk−∑i=0k−1ak−iλif(\lambda)=\lambda^k-\sum_{i=0}^{k-1}a_{k-i}\lambda^if(λ)=λki=0k1akiλi
然后由 Cayley−HamiltonCayley-HamiltonCayleyHamilton 定理
f(A)=0f(A)=0f(A)=0,一个巧妙的伪证是 f(A)=∣AI−A∣=0f(A)=|AI-A|=0f(A)=AIA=0
R(A)=An%f(A)R(A)=A^n\% f(A)R(A)=An%f(A),即 g(A)∗f(A)+R(A)=Ang(A)*f(A)+R(A)=A^ng(A)f(A)+R(A)=An
又因为 f(A)=0f(A)=0f(A)=0 ,所以 An=R(A)A^n=R(A)An=R(A)
看看求的是什么,是向量 vvv,乘上 AnA^nAn 的第 k 列,令f(A)f(A)f(A) 的各项系数为 ccc
(v∗An)k=(v∗∑i=0kciAi)k=(∑i=0kci∗(v∗Ai))k(v*A^n)_k=(v*\sum_{i=0}^kc_iA^i)_k=(\sum_{i=0}^k c_i*(v*A^i))_k(vAn)k=(vi=0kciAi)k=(i=0kci(vAi))k
初始向量 vvv 乘上转移矩阵 iii 次,即为给出的 fif_ifi
于是 ans=∑i=0kci∗fians=\sum_{i=0}^kc_i*f_ians=i=0kcifi
ccc即特征多项式的系数可以用 AnA^nAnf(λ)=λk−∑i=0k−1ak−iλif(\lambda)=\lambda^k-\sum_{i=0}^{k-1}a_{k-i}\lambda^if(λ)=λki=0k1akiλi 多项式取模得出
复杂度 O(k∗log(k)∗log(n))O(k*log(k)*log(n))O(klog(k)log(n))
主要的思想就是不必求出 AnA^nAn,将AnA^nAn 转换成∑i=0kciAi\sum_{i=0}^k c_iA^ii=0kciAi
并且也不求 AiA^iAi,而是用初始向量去乘它∑i=0kci(Ai∗v)\sum_{i=0}^k c_i (A_i*v)i=0kci(Aiv),得到了我们想要的答案

#include<bits/stdc++.h>
#define N 400050
using namespace std;
typedef long long ll;
const int Mod = 998244353, G = 3;
ll add(ll a, ll b){ return a + b >= Mod ? a + b - Mod : a + b;}
ll mul(ll a, ll b){ return a * b % Mod;}
ll power(ll a, ll b){ ll ans = 1;
	for(;b;b>>=1){if(b&1) ans = mul(ans, a); a = mul(a, a);}
	return ans;
}
ll inv[N]; int n, k;
#define poly vector<ll>
#define C 20
poly w[C + 1];
int up, bit, rev[N]; ll a[N];
void Init(int len){ up = 1, bit = 0;	
	while(up < len) up <<= 1, bit++;
	for(int i = 0; i < up; i++) rev[i] = (rev[i>>1]>>1)|((i&1)<<(bit-1));
}
void prework(){
	inv[0] = inv[1] = 1;
	for(int i = 2; i <= N-50; i++) inv[i] = mul(Mod-Mod/i, inv[Mod%i]);
	for(int i = 1; i <= C; i++) w[i].resize(1<<(i-1));
	ll wn = power(G, (Mod-1)/(1<<C)); w[C][0] = 1; 
	for(int i = 1; i < (1<<(C-1)); i++) w[C][i] = mul(w[C][i-1], wn);
	for(int i = C-1; i; i--)
		for(int j = 0; j < (1<<(i-1)); j++)
			w[i][j] = w[i+1][j<<1];
}
void NTT(poly &a, int flag){
	for(int i = 0; i < up; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
	for(int i = 1, l = 1; i < up; i <<= 1, l++)
		for(int j = 0; j < up; j += (i<<1))
			for(int k = 0; k < i; k++){
				ll x = a[k + j], y = mul(w[l][k], a[k + j + i]);
				a[k + j] = add(x, y); a[k + j + i] = add(x, Mod-y);
			}
	if(flag == -1){
		reverse(a.begin() + 1, a.begin() + up);
		for(int i = 0; i < up; i++) a[i] = mul(a[i], inv[up]);
	}
}
poly operator * (poly a, poly b){
	int len = a.size() + b.size() - 1;
	Init(len); a.resize(up); b.resize(up);
	NTT(a, 1); NTT(b, 1);
	for(int i = 0; i < up; i++) a[i] = mul(a[i], b[i]); 
	NTT(a, -1); a.resize(len); return a;
}
poly Inv(poly a, int len){
	poly b(1, power(a[0], Mod - 2)), c;
	for(int lim = 4; lim < (len << 2); lim <<= 1){
		Init(lim); c = a; c.resize(lim >> 1);
		c.resize(up); b.resize(up); NTT(c, 1); NTT(b, 1);
		for(int i = 0; i < up; i++) b[i] = mul(b[i], add(2, Mod - mul(b[i], c[i])));
		NTT(b, -1); b.resize(lim >> 1);
	} b.resize(len); return b;
}
poly operator - (poly a, poly b){
	poly c; int len = max(a.size(), b.size()); c.resize(len);
	a.resize(len); b.resize(len);
	for(int i = 0; i < len; i++) c[i] = add(a[i], Mod - b[i]);
	return c;
}
poly operator / (poly a, poly b){
	int lim = 1, len = a.size() - b.size() + 1;
	reverse(a.begin(), a.end()); reverse(b.begin(), b.end());
	while(lim <= len) lim <<= 1;
	b = Inv(b, lim); b.resize(len);
	a = a * b; a.resize(len);
	reverse(a.begin(), a.end()); 
	return a;
}
poly operator % (poly a, poly b){ 
	int len = a.size() - b.size() + 1;
	if(len < 0) return a;
	poly c = a - (a / b) * b;
	c.resize(b.size() - 1); return c;
}
int main(){
	scanf("%d%d", &n, &k); prework();
	poly f(k + 1);
	for(int i = 1; i <= k; i++) scanf("%lld", &f[k - i]), f[k - i] = Mod - (f[k - i] % Mod + Mod) % Mod;
	f[k] = 1;
	for(int i = 0; i < k; i++) scanf("%lld", &a[i]), a[i] = (a[i] % Mod + Mod) % Mod;
	poly res, g;
	res.resize(k + 1); res[0] = 1;
	g.resize(k + 1); g[1] = 1;
	for(;n;n>>=1, g = g * g % f) if(n & 1) res = res * g % f;
	ll ans = 0;
	for(int i = 0; i < res.size(); i++) ans = add(ans, mul(res[i], a[i])); 
	cout << ans; 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值