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

传送门

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


【分析】

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

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

现考虑如何求解 (g_t)

(egin{aligned} &sum_{k=0}^n k^tcdot x^k cdot inom n k \\=&sum_{k=0}^n k^{t-1}kcdot x^{k-1} cdot xcdot inom n k \\=&xcdot { ext dover ext dx}(sum_{k=0}^n k^{t-1}cdot x^kcdot inom n k) end{aligned})

(p(x)=x+1)(g_0=p^n(x))(displaystyle g_t=xcdot { ext dover ext dx}g_{t-1})

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

(egin{aligned} &xcdot { ext dover ext dx}f_np^n(x) \\=&f_ncdot [p(x)-1]cdot np^{n-1}(x) \\=&nf_ncdot p^n(x)-nf_ncdot 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))

由于 (mleq 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) 范围内