洛谷·[省选联考 2020 A 卷] 组合数问题

初见安~这里是传送门:洛谷P6620 [省选联考 2020 A 卷] 组合数问题

题解

看到题目中的x^k很容易想到用第二类斯特林数来把k换出来降低复杂度。但是显然k也是1e9的范围所以这就meaningless了。

但是f函数中还有一个。并且m很小,只有1000。

f(k)=\sum_{i=0}^ma_ik^i

由第二类斯特林数的式子:

S(i,j)=\frac{1}{j!}\sum_{k=0}^j(-1)^{j-k}C_{j}^kk^i

可以二项式反演得到:

k^i=\sum_{j=0}^iS(i,j)C_k^jj!

于是把这个反演式子带进原式子:

\dpi{120} f(k)=\sum_{i=0}^ma_ik^i\\ =\sum_{i=0}^ma_i\sum_{j=0}^iS(i, j)C_k^jj!\\ =\sum_{j=0}^kC_k^jj!\sum_{i=j}^ma_iS(i,j)\\ =k!\sum_{j=0}^k\frac{1}{(k-j)!}\sum_{i=j}^ma_iS(i,j)\\

我们先把后面的sum代换一下,然后把f带入原式子:

g(j)=\sum_{i=j}^ma_i S(i,j)\\ \sum_{k=0}^nf(k)x^kC_n^k\\ =\sum_{k=0}^nk!\sum_{j=0}^k\frac{1}{(k-j)!}g(j)x^kC_n^k\\ =\sum_{k=0}^n\frac{n!x^k}{(n-k)!}\sum_{j=0}^k\frac{g(j)}{(k-j)!}\\

到这里其实还不能算。因为这个题的模数不一定是质数,所以没法预处理阶乘逆元。那就继续推:

\\ =\sum_{j=0}^nn!g(j)\sum_{k=j}^n\frac{x^k}{(k-j)!(n-k)!}\\ =\sum_{j=0}^nn!g(j)\sum_{k=0}^{n-j}\frac{x^{k+j}}{k!(n-k-j)!}\\ =\sum_{j=0}^n\frac{n!g(j)x^j}{(n-j)!}\sum_{k=0}^{n-j}\frac{x^k(n-j)!}{k!(n-k-j)!}\\ =\sum_{j=0}^ng(j)x^jn^{\underline{j}}(x+1)^{n-j}\\

然后因为式子里的g(j)里,j \leq m,所以j的枚举上界其实是m。n的j次下降幂可以暴力算,斯特林数可以递推预处理,这样的话最后总的复杂度就是O(m^2)

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxm 1003
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, x, mod, m, a[maxm], S[maxm][maxm], g[maxm];
int pw(int a, int b) {int res = 1; while(b) {if(b & 1) res = 1ll * res * a % mod; a = 1ll * a * a % mod, b >>= 1;} return res;}
signed main() {
	n = read(), x = read(), mod = read(), m = read();
	for(int i = 0; i <= m; i++) a[i] = read();
	
	S[0][0] = 1;
	for(int i = 1; i <= m; i++) for(int j = 1; j <= m; j++) //斯特林数递推式
		S[i][j] = (1ll * j * S[i - 1][j] % mod + S[i - 1][j - 1]) % mod;
	//为了方便枚举,g做了一下移位
	for(int j = 0; j <= m; j++) for(int i = 0; i <= m - j; i++) 
		g[m - j] = (1ll * g[m - j] + 1ll * a[i + j] * S[i + j][j] % mod) % mod;
		
	int ans = 0;
	for(int i = 0; i <= m; i++) {
		int tmp = 1;
		for(int j = n - i + 1; j <= n; j++) tmp = 1ll * tmp * j % mod;//求下降幂
		ans = (1ll * ans + 1ll * g[m - i] * tmp % mod * pw(x + 1, n - i) % mod * pw(x, i) % mod) % mod;
	}
	printf("%d\n", ans);
	return 0;
}

公式可能有打错了的……迎评:)
——End——

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值