[CQOI2018] 交错序列(矩阵加速优化dp)

problem

luogu-P4456

solution

预处理阶乘和阶乘的逆元,枚举 1 1 1 出现次数 i i i ∑ ( n − i + 1 i ) ( n − i ) a i b \sum\binom{n-i+1}{i}(n-i)^ai^b (ini+1)(ni)aib

  • ( n − i + 1 i ) \binom{n-i+1}{i} (ini+1) 如何推出来?

    n n n 个中选 i i i ( n i ) \binom ni (in)。容斥不太可能。

    隔板法。分成 i i i 堆需要插 i − 1 i-1 i1 块板。

    考虑 1 1 1 不能连续也就是说两块板之间至少要隔两个盒子。

    隔板法经典强制每个至少有 1 1 1 个的方法类比过来。

    发现只需要拿走 i − 1 i-1 i1 个盒子,剩下的选的位置就可以相邻。

但这样是不行的,快速幂部分耗时大,且 n , m n,m n,m 之间大小关系不定,逆元可能不存在,无法预处理。

考虑解决组合数计算部分。

  • lucas \text{lucas} lucas 定理算。

    不能在要求时间内通过。

  • 算一个组合数,可以将分子分母质因数分解,然后在指数位置进行加减运算。最后在把所有质因数乘起来,就可以巧妙避免计算逆元的问题。

    但本题组合数要求计算多个,时间开销依然未能缩减。

考虑解决快速幂计算部分。

  • x a , y b x^a,y^b xa,yb 都是完全积性函数。

    f ( i ) = i a ⇒ f ( x y ) = ( x y ) a = f ( x ) f ( y ) = x a y a f(i)=i^a\Rightarrow f(xy)=(xy)^a=f(x)f(y)=x^ay^a f(i)=iaf(xy)=(xy)a=f(x)f(y)=xaya

    所以可以 O ( n ) O(n) O(n) 线性筛。

    这是解决幂运算指数固定的常见方法。

这种做法是不能通过本题的,洛谷题解有一篇能过完全是数据问题。这里只是想记录一些 trick \text{trick} trick


考虑计算贡献 x a y b = ( n − y ) a y b = ∑ i = 0 a ( a i ) n i ( − 1 ) a − i y a − i y b x^ay^b=(n-y)^ay^b=\sum_{i=0}^a\binom ain^i(-1)^{a-i}y^{a-i}y^b xayb=(ny)ayb=i=0a(ia)ni(1)aiyaiyb

发现当枚举 i i i ( a i ) n i ( − 1 ) a − i \binom{a}{i}n^i(-1)^{a-i} (ia)ni(1)ai 均为常数,唯一随序列不同而变化的是 y a + b − i y^{a+b-i} ya+bi,准确来说应该是 y y y

我们可以计算所有序列中 1 1 1 的个数的 a + b − i a+b-i a+bi 次方之和,然后就可以计入答案。

f ( i , j , k ) : f(i,j,k): f(i,j,k): 考虑前 i i i 位,第 i i i 位为 k ∈ [ 0 , 1 ] k\in[0,1] k[0,1],所有合法序列的 1 1 1 的个数的 j j j 次方之和,即 ∑ y j \sum y^j yj

  • i i i 0 0 0,则对前面无限制。没有新增的 1 1 1 的个数,贡献不变。

    f ( i , j , 0 ) = f ( i − 1 , j , 0 ) + f ( i − 1 , j , 1 ) f(i,j,0)=f(i-1,j,0)+f(i-1,j,1) f(i,j,0)=f(i1,j,0)+f(i1,j,1)

  • i i i 1 1 1,则前一位不能为 1 1 1,只能从 0 0 0 转移。

    此时将有 y j → ( y + 1 ) j y^j\rightarrow (y+1)^j yj(y+1)j 直接二项式展开。

    ( y + 1 ) j = ∑ k = 0 j ( j k ) y k 1 j − k (y+1)^j=\sum_{k=0}^j\binom jky^k1^{j-k} (y+1)j=k=0j(kj)yk1jk

    所有序列的 y k y^k yk 之和恰恰是 f ( , k , ) f(,k,) f(,k,) 的定义。

    所以转移为: f ( i , j , 1 ) = ∑ k = 0 j ( j k ) f ( i − 1 , k , 0 ) f(i,j,1)=\sum_{k=0}^j\binom jkf(i-1,k,0) f(i,j,1)=k=0j(kj)f(i1,k,0)

发现转移压根和 i i i 这一维没有关系,所以是可以矩阵加速 n n n 的。

注意我们要计算到 y a + b y^{a+b} ya+b 次方,且我们将两种转移合并在一起。

构造初始矩阵 f : [ f 0 , 0 , f 1 , 0 , . . . , f a + b , 0 , f 0 , 1 , f 1 , 1 , . . . , f a + b , 1 ] f:[f_{0,0},f_{1,0},...,f_{a+b,0},f_{0,1},f_{1,1},...,f_{a+b,1}] f:[f0,0,f1,0,...,fa+b,0,f0,1,f1,1,...,fa+b,1]。形式化为 [ f j , 0 ∣ f j , 1 ] , j ∈ [ 0 , a + b ] [f_{j,0}\mid f_{j,1}],j\in[0,a+b] [fj,0fj,1],j[0,a+b]

构造加速矩阵 g g g:分拆为四个部分。

  • 左上角为单位矩阵。表示 f ( , 0 ) → f ′ ( , 0 ) f(,0)\rightarrow f'(,0) f(,0)f(,0)
  • 左下角为单位矩阵。表示 f ( , 1 ) → f ′ ( , 0 ) f(,1)\rightarrow f'(,0) f(,1)f(,0)
  • 右上角为组合数矩阵。注意是行列交换了的,表示 f ( , 0 ) → f ′ ( , 1 ) f(,0)\rightarrow f'(,1) f(,0)f(,1)
  • 右下角为全 0 0 0 矩阵。表示不合法 f ( , 1 ) → f ′ ( , 1 ) f(,1)\rightarrow f'(,1) f(,1)f(,1)

具体可以自己画一下,发现是匹配的。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 185
int n, a, b, mod, m1, m2;
int C[maxn][maxn];
struct matrix {
	int c[maxn][maxn];
	matrix() { memset( c, 0, sizeof( c ) ); }
	matrix operator * ( matrix &v ) {
		matrix ans;
		for( int i = 0;i < m2;i ++ )
			for( int k = 0;k < m2;k ++ )
				if( c[i][k] ) //稀疏矩阵经典有效优化
					for( int j = 0;j < m2;j ++ ) //j,k交换 内存访问连续 优化常数
						ans.c[i][j] = (ans.c[i][j] + c[i][k] * v.c[k][j]) % mod;
		return ans;
	}	
}g, f;

signed main() {
	scanf( "%lld %lld %lld %lld", &n, &a, &b, &mod );
	m1 = a + b + 1, m2 = m1 << 1;
	for( int i = 0;i <= m1;i ++ ) {
		C[i][0] = C[i][i] = 1;
		for( int j = 1;j < i;j ++ )
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
	}
	for( int i = 0;i < m1;i ++ ) {
		g.c[i][i] = g.c[i + m1][i] = 1;
		for( int j = i;j < m1;j ++ )
			g.c[i][j + m1] = C[j][i];
	}
	f.c[0][0] = 1; int x = n;
	while( x ) {
		if( x & 1 ) f = f * g;
		g = g * g;
		x >>= 1;
	}
	x = 1; int ans = 0;
	for( int i = 0;i <= a;i ++, x = x * n % mod )
		if( (a - i) & 1 )
			(ans -= (f.c[0][a+b-i] + f.c[0][a+b-i+m1]) % mod * C[a][i] % mod * x) %= mod;
		else 
			(ans += (f.c[0][a+b-i] + f.c[0][a+b-i+m1]) % mod * C[a][i] % mod * x) %= mod;
	printf( "%lld\n", (ans + mod) % mod );
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值