题解 luogu P6620 [省选联考 2020 A 卷] 组合数问题

传送门

提供一个不用下降幂也不用斯特林数的做法


【分析】

\(\begin{aligned} &\sum_{k=0}^n f(k)\cdot x^k \cdot \binom n k \\\\=&\sum_{k=0}^n \sum_{i=0}^m a_ik^i\cdot x^k \cdot \binom n k \\\\=&\sum_{i=0}^m a_i(\sum_{k=0}^n k^i\cdot x^k \cdot \binom n k) \end{aligned}\)

\(\displaystyle g_t=\sum_{k=0}^n k^t\cdot x^k \cdot \binom n k\) ,结果显然为 \(\displaystyle \sum_{i=0}^m a_ig_i\)

现考虑如何求解 \(g_t\)

\(\begin{aligned} &\sum_{k=0}^n k^t\cdot x^k \cdot \binom n k \\\\=&\sum_{k=0}^n k^{t-1}k\cdot x^{k-1} \cdot x\cdot \binom n k \\\\=&x\cdot {\text d\over \text dx}(\sum_{k=0}^n k^{t-1}\cdot x^k\cdot \binom n k) \end{aligned}\)

\(p(x)=x+1\)\(g_0=p^n(x)\)\(\displaystyle g_t=x\cdot {\text d\over \text dx}g_{t-1}\)

考虑到求导的线性性质,有:

\(\begin{aligned} &x\cdot {\text d\over \text dx}f_np^n(x) \\\\=&f_n\cdot [p(x)-1]\cdot np^{n-1}(x) \\\\=&nf_n\cdot p^n(x)-nf_n\cdot p^{n-1}(x) \end{aligned}\)

\(g_t\) 本质上就是一个关于 \(p(x)\) 的多项式,只要将求导法则重定义为上述求导法则即可

故初始化多项式为 \(f_n=1\) ,之后每次都按上述法则求导,再求值,即可求出 \(g_0\)~\(g_t\)

\(n\) 非常大,无法直接存下。考虑到初始时最低次项为 \(p^n(x)\) ,求导一次后最低次项为 \(p^{n-1}(x)\) ,迭代可得,求导 \(m\) 次后最低次项为 \(p^{n-m}(x)\)

由于 \(m\leq 10^3\) ,所以直接开 \(O(m)\) 的空间,计算的时候偏移 \((n-m)\) 即可;同时,每次计算的时候从 \((n-m)\) 次方开始带值即可

故总复杂度为 \(O(m^2)\)


【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
typedef double db;
#define fi first
#define se second
const int MAXN=1024;
int n, x, p, m, powit[MAXN], a[MAXN], f[MAXN], g[MAXN];
inline ll fpow(ll a,ll x) { ll ans=1; for(;x;x>>=1,a=a*a%p) if(x&1) ans=ans*a%p; return ans; }
inline void init() {
	cin>>n>>x>>p>>m;
	powit[0]=fpow(x+1, n-m);
	for(int i=1; i<=m; ++i)
		powit[i]=(ll)powit[i-1]*(x+1)%p;
	f[m]=1;
	g[0]=powit[m];
	for(int t=1; t<=m; ++t) {
		f[0]=0;
		for(int i=1; i<=m; ++i)
			f[i-1]=(f[i-1]-(f[i]=(ll)f[i]*(n-m+i)%p))%p;
		for(int i=0; i<=m; ++i)
			g[t]=(g[t]+(ll)f[i]*powit[i])%p;
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	init();
	int res=0;
	for(int i=0; i<=m; ++i)
		cin>>a[i], res=(res+(ll)a[i]*g[i])%p;
	res=(res+p)%p;
	cout<<res;
	cout.flush();
	return 0;
}

运算过程中,只要保证运算结果在 \((-p,p)\) 范围内,最后统一转入 \(Z_p\) 范围内

posted @ 2021-09-20 23:12  JustinRochester  阅读(43)  评论(0编辑  收藏  举报