problem
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 ∑(in−i+1)(n−i)aib。
-
( n − i + 1 i ) \binom{n-i+1}{i} (in−i+1) 如何推出来?
从 n n n 个中选 i i i 个 ( n i ) \binom ni (in)。容斥不太可能。
隔板法。分成 i i i 堆需要插 i − 1 i-1 i−1 块板。
考虑 1 1 1 不能连续也就是说两块板之间至少要隔两个盒子。
隔板法经典强制每个至少有 1 1 1 个的方法类比过来。
发现只需要拿走 i − 1 i-1 i−1 个盒子,剩下的选的位置就可以相邻。
但这样是不行的,快速幂部分耗时大,且 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)=ia⇒f(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=(n−y)ayb=∑i=0a(ia)ni(−1)a−iya−iyb。
发现当枚举 i i i 时 ( a i ) n i ( − 1 ) a − i \binom{a}{i}n^i(-1)^{a-i} (ia)ni(−1)a−i 均为常数,唯一随序列不同而变化的是 y a + b − i y^{a+b-i} ya+b−i,准确来说应该是 y y y。
我们可以计算所有序列中 1 1 1 的个数的 a + b − i a+b-i a+b−i 次方之和,然后就可以计入答案。
设 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(i−1,j,0)+f(i−1,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)yk1j−k。
所有序列的 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(i−1,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,0∣fj,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;
}