常系数齐次线性递推学习小记

15 篇文章 0 订阅
8 篇文章 0 订阅

适用范围:

若遇到形如此类的递推式: f i = ∑ j = 1 k f i − j ∗ a j f_i=\sum_{j=1}^kf_{i-j}*a_j fi=j=1kfijaj
我们称其为线性递推。遇到此类问题,我们可以用矩阵快速幂求第 n n n项,复杂度是 O ( k 3 log ⁡ n ) 。 O(k^3\log{n})。 O(k3logn)
这个复杂度在 k k k比较大的时候很吃力,我们就有了一种优化方案:
也就是常系数齐次线性递推。

具体推导(不含定理证明):

我们设其转移矩阵为 A A A
初始向量为: S T = ST= ST={ f 0 , f 1 . . . . . . , f k − 1 f_0,f_1......,f_{k-1} f0,f1......,fk1}。
我们最终要求的就是: S T ∗ A n ST*A^n STAn
我们考虑把 A n A^n An表示成如下形式 A n = ∑ i = 0 k − 1 c i A i A^n=\sum_{i=0}^{k-1}c_iA^i An=i=0k1ciAi
那么我们要求的就是 ∑ i = 0 k − 1 c i ∗ S T ∗ A i \sum_{i=0}^{k-1}c_i*ST*A^i i=0k1ciSTAi。(乘法满足分配律)。
考虑到 S T ∗ A i = S T i ST*A^i=ST_i STAi=STi。相当于初始向量转移 i i i次。
那么也就是说我们只要求出 c i ci ci,那么只要将初始向量对应乘上去就可以得到第 n n n项了。
这个怎么求呢?
我们可以把一个多项式表示成如下形式: A n = C ( x ) = P ( x ) Q ( x ) + R ( x ) A^n=C(x)=P(x)Q(x)+R(x) An=C(x)=P(x)Q(x)+R(x)。(在这里, x x x是一个矩阵。)
若当 x = A x=A x=A时, Q ( x ) Q(x) Q(x)是零向量,那么问题转化为: C ( x ) ≡ R ( x ) ( m o d Q ( x ) ) C(x) \equiv R(x) (mod Q(x) ) C(x)R(x)(modQ(x))
我们只需要快速幂加多项式取模即可求出 C ( x ) C(x) C(x) c i c_i ci
这样的 Q ( x ) Q(x) Q(x)一定存在,但我好像不太会证。
结论就是: Q k = 1 , Q i = − a k − i ( 0 ≤ i ≤ k − 1 ) Q_k=1,Q_i=-a_{k-i} (0\leq i \leq k - 1) Qk=1,Qi=aki(0ik1)。这里的 a i a_i ai是递推系数。
暴力多项式取模复杂度是 O ( k 2 log ⁡ n ) O(k^2 \log n) O(k2logn)
N T T NTT NTT可以优化到 O ( k log ⁡ k log ⁡ n ) O(k \log k \log n) O(klogklogn)

例题 NOI2017 泳池:

题意:有一个长度为 N N N,高度为 1001 1001 1001的网格图。每一个格子有 q q q的概率是安全的。
我们定义一个合法的子矩形为:矩形中格子都是安全的,且矩形下边界贴着网格图下边界。
求最大子矩形面积为 K K K的概率。

数据范围: n ≤ 1 0 9 , k ≤ 1 0 3 n \leq 10^9,k \leq 10^3 n109,k103

题解:
为了方便 D P DP DP,我们用最大面积至多为 K K K的概率减去面积至多为 K − 1 K-1 K1的概率。
考虑逐列递推,设 f i fi fi表示到第 i i i列的答案。考虑添加一个最大子矩形,枚举矩形的长。
需要其他 D P DP DP辅助转移。
设一个 g i , j , h i , j g_{i,j},h_{i,j} gi,j,hi,j分别表示长度为 j j j,高度 i i i以下全为安全格,以上任意,但最大子矩形面积不超过 k k k的概率,长度为 j j j,高度 i i i以下全为安全格,且第 i + 1 i+1 i+1行最右边的格子不安全的,最大子矩形面积不超过 k k k的概率。
枚举子矩形在哪里被一个不合法格子隔开,可以得到转移。
初始化: g k , 1 = q k ∗ ( 1 − q ) , g i , 0 = h i , 0 = 1 g_{k,1}=q^k*(1-q),g_{i,0}=h_{i,0}=1 gk,1=qk(1q),gi,0=hi,0=1
g i , j = ∑ l = 0 j h i , l ∗ g i + 1 , j − l g_{i,j}=\sum_{l=0}^jh_{i,l}*g_{i+1,j - l} gi,j=l=0jhi,lgi+1,jl h i , j = ∑ l = 0 j − 1 h i , l ∗ g i + 1 , j − l − 1 ∗ ( 1 − q ) ∗ q i h_{i,j}=\sum_{l=0}^{j-1}h_{i,l}*g_{i+1,j-l-1}*(1-q)*q^i hi,j=l=0j1hi,lgi+1,jl1(1q)qi
此时我们就可以递推 f f f了。 f i = ∑ j = 1 k f i − j − 1 ∗ g 1 , j ∗ ( 1 − q ) f_i=\sum_{j=1}^kf_{i-j-1}*g_{1,j}*(1-q) fi=j=1kfij1g1,j(1q)
我们设 a i = g 1 , i − 1 ∗ ( 1 − q ) a_i=g_{1,i-1}*(1-q) ai=g1,i1(1q)。就有: f i = ∑ j = 1 k + 1 f i − j ∗ a j f_i=\sum_{j=1}^{k+1}f_{i-j}*a_j fi=j=1k+1fijaj
这是一个线性递推式,暴力多项式取模快速幂优化即可。
复杂度: O ( k 2 log ⁡ n ) O(k^2 \log {n}) O(k2logn)

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 1e3 + 5;
const int mo = 998244353;
typedef long long ll;
int g[N][N],h[N][N],a[N],f[N],q[N];
int t[N << 1],c[N << 1],d[N << 1],tmp[N << 1];
inline int pow(int x,int p)
{
	int ret = 1;
	for (; p ; p >>= 1,x = (ll)x * x % mo)
	if (p & 1) ret = (ll)ret * x % mo;
	return ret;
}
inline void mul(int *a,int *b,int len)
{
	memset(tmp,0,sizeof(tmp));
	for (int i = 0 ; i <= len ; ++i)
		for (int j = 0 ; j <= len ; ++j) tmp[i + j] = (tmp[i + j] + (ll)a[i] * b[j] % mo) % mo;
	for (int i = 0 ; i <= (len << 1) ; ++i) a[i] = tmp[i];
}
inline void mod(int *a,int fir,int sec)
{
	for (int i = fir ; i > sec ; --i)
	{
		int x = a[i];
		for (int j = i - sec - 1 ; j <= i ; ++j) a[j] = (a[j] - (ll)x * t[j - i + sec + 1] % mo + mo) % mo;
	}
}
inline int solve(int n,int K)
{
	if (!K) return pow(mo + 1 - q[0],n);
	memset(g,0,sizeof(g)),memset(h,0,sizeof(h));
	g[K][1] = (ll)q[K - 1] * (mo + 1 - q[0]) % mo,g[K][0] = 1;
	for (int i = K - 1 ; i ; --i)
	{
		int lim = K / i; g[i][0] = h[i][0] = 1;
		for (int j = 1 ; j <= lim ; ++j)
			for (int k = 0 ; k < j ; ++k) h[i][j] = (h[i][j] + (ll)h[i][k] * g[i + 1][j - 1 - k] % mo * q[i - 1] % mo * (mo + 1 - q[0]) % mo) % mo;
		for (int j = 1 ; j <= lim ; ++j)
			for (int k = 0 ; k <= j ; ++k) g[i][j] = (g[i][j] + (ll)h[i][k] * g[i + 1][j - k] % mo) % mo;
	}
	for (int i = 1 ; i <= K + 1 ; ++i) a[i] = (ll)g[1][i - 1] * (mo + 1 - q[0]) % mo;
	f[0] = t[K + 1] = 1;
	for (int i = K ; ~i ; --i) t[i] = mo - a[K + 1 - i];
	memset(c,0,sizeof(c)),memset(d,0,sizeof(d));
	int p = n; c[d[0] = 1] = 1;
	for (; p ; p >>= 1,mul(c,c,K),mod(c,K << 1,K))
	if (p & 1) mul(d,c,K),mod(d,K << 1,K);
	for (int i = 1 ; i <= K ; ++i)
	{
		f[i] = g[1][i];
		for (int j = 1 ; j <= i ; ++j) f[i] = (f[i] + (ll)f[i - j] * a[j] % mo) % mo;
	}
	int ret = 0; for (int i = 0 ; i <= K ; ++i) ret = (ret + (ll)f[i] * d[i] % mo) % mo;
	return ret;
}
int main()
{
	int n,K,x,y; scanf("%d%d%d%d",&n,&K,&x,&y); q[0] = (ll)x * pow(y,mo - 2) % mo;
	for (int i = 1 ; i <= K - 1 ; ++i) q[i] = (ll)q[i - 1] * q[0] % mo;
	printf("%d\n",(solve(n,K) - solve(n,K - 1) + mo) % mo);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值