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

题目

计算 \(\left(\sum\limits_{k=0}^n f(k) \times x^k \times \dbinom{n}{k}\right)\bmod p\)\(f(k) = \sum\limits_{i=0}^m a_i \times k^i\) , \(n,k,p,m,a_i\) 给定。

前言

前几天考了一道类似的题目,当时本蒟蒻考场自闭,考后,听说有此题,便做了一下,因为我比较菜,所以这篇题解主要以一个不会多项式(虽然此题不要)、没怎么推过式子的蒟蒻角度做题。

前置基础

1.会组合数的定义,知道其性质,怎么求

2.了解斯特林数,会它的通项公式,一些小性质就行了

3.知道二项式定理(认得长什么样就行了)

(这些东西具体性质后面会讲,会解释,只要了解就可以继续看下去了)

思路

先合并成一个式子:

\(\left(\sum\limits_{k=0}^n \sum\limits_{i=0}^m a_i \times k^i\times x^k \times \dbinom{n}{k}\right)\bmod p\)

我们再看一下数据范围

\(n \in [1,10^9],k \in[0,n], m \in [0,min(n,1000)]\)

那么,我们一定不能去暴力算 \(k\) ,我们要想办法将有关 \(k\) 的项全部变成 与 \(m\) (或和 \(m\) 一样小的) 有关的项。

\(p\) 不一定是质数

这题很可能不是多项式

因为 \(n\) 很大,我们只能处理出 \(k!\bmod p\)\(k!\)\(p\) 下的逆元(这是一种组合数的求法)。但 \(p\) 不是质数,有的数没有逆元,我们就要想办法除去组合数。(这里可能有点不严谨,但反正很难求出这个组合数)

综上,

我们有两个目标:

目标一

将有关 \(k\) 的项全部变成 与 \(m\) (或和 \(m\) 一样小的) 有关的项。

目标二

除去组合数

具体步骤

\(\left(\sum\limits_{k=0}^n \sum\limits_{i=0}^m a_i \times k^i\times x^k \times \dbinom{n}{k}\right)\bmod p\)

对于目标一

\(k^i\) 最难搞,我们先消去 \(k^i\) ,这里要用第二类斯特林数,\(\left\{_m^n \right\}\) 代表 \(n\) 个球放入 \(m\) 个集合中的方案数。

\(k^i = \sum\limits_{j = 0}^k \left\{^i_j\right\} \dbinom{k}{j}j!\)

考虑组合意义: \(k^i\) 代表将\(i\) 个球放入\(k\) 个互不相同盒子中(可以为空),

我们这个展开就是先讨论要将这些球放入\(j\) 个相同盒子中(不能为空),即选盒子的方案数乘上将球放入这些盒子的方案数: \(\left\{^i_j\right\} \dbinom{k}{j}\)

又因为我们要求的是不同的盒子中,我们再乘上一个 \(j!\)

值得注意的是,当 \(j > i\) 时, \(\left\{^i_j\right\}\) 没有意义,为了不择手段地 消去 \(k\) ,我们可以将求和上界改为 \(i\)

\(k^i = \sum\limits_{j = 0}^i \left\{^i_j\right\} \dbinom{k}{j}j!\)

至于 \(x^k\) 这样展开复杂了,我们先放到一边。

对于目标二

我们的式子已经变成了:

\(\left(\sum\limits_{k=0}^n \sum\limits_{i=0}^m a_i \times \sum\limits_{j = 0}^i \left\{^i_j\right\} \dbinom{k}{j}j!\times x^k \times \dbinom{n}{k}\right)\bmod p\)

我们可以看到有两个组合数:

\(\dbinom{k}{j} \times \dbinom{n}{k}\)

再次考虑组合意义:

先从 \(n\) 个球中选出 \(k\) 个球,再从中选出 \(j\) 个球,

这就等价于

先选出 \(j\) 个球作为最后的方案,再从选 \(n-j\) 个球中选出 \(k-j\) 个球作为第一次还要选的球,

所以,

\(\dbinom{k}{j} \times \dbinom{n}{k} = \dbinom{n-j}{k-j} \times \dbinom{n}{j}\)

但是组合数还是没有消失,我们的式子变成了:

\(\left(\sum\limits_{k=0}^n \sum\limits_{i=0}^m a_i \times \sum\limits_{j = 0}^i \left\{^i_j\right\} \dbinom{n-j}{k-j} \times \dbinom{n}{j}j!\times x^k \right)\bmod p\)

调一下顺序:

\(\left( \sum\limits_{i=0}^m a_i\sum\limits_{j=0}^i\left\{^i_j\right\}\times\dbinom{n}{j}j!\times \sum\limits_{k=0}^n\dbinom{n-j}{k-j}x^k \right)\bmod p\)

接下来,非常重要,

观察到

\(\dbinom{n-j}{k-j}x^k\)

我们知道二项式定理:

\((x+y)^n = \sum\limits_{i =0}^n \dbinom{n}{i}\times x^i y^{n-i}\) ,那么,我们可以尽量让上面的式子变为二项式定理,

如果是这样就好了:

\(\sum\limits_{k=0}^n\dbinom{n-j}{k-j}x^{k-j} = (x+1)^{n-j}\) (二项式定理)

那我们就强行补一下:

\(\sum\limits_{k=0}^n\dbinom{n-j}{k-j}x^{k} = \sum\limits_{k=0}^n\dbinom{n-j}{k-j}x^{k-j}\times x^j = (x+1)^{n-j}\times x^j\)

原式简单多了:

\(\left( \sum\limits_{i=0}^m a_i\sum\limits_{j=0}^i\left\{^i_j\right\}\times\dbinom{n}{j}j!\times (x+1)^{n-j}\times x^j \right)\bmod p\)

离目标最后一步!

\(\dbinom{n}{j}j! = \frac{n!}{j!\times(n-j)!}\times j! = \frac{n!}{(n-j)!} = n \times (n-1) \times \dots\times(n-j+1)\)

因为 \(j\) 十分小,我们就可以再循环的时候一边计算这个式子就行了。

答案:

\(\left( \sum\limits_{i=0}^m a_i\sum\limits_{j=0}^i\left\{^i_j\right\}\times\frac{n!}{(n-j)!}\times (x+1)^{n-j}\times x^j \right)\bmod p\)

我们只要 \(O(k^2)\) 推斯特林数就行了,其他项在循环时可以算出来。

代码

const int MAXN = 5010;

typedef long long ll;

ll a[MAXN],strling[MAXN][MAXN],n,x,p,m,x1j[MAXN];

ll qp(ll x,ll t){
    ll res = 1;
    for(;t;t >>=  1,x = x*x%p){
	if(t&1) res = res * x % p;
    }
    return res;
}

int main (){
    scanf("%lld %lld %lld %lld",&n,&x,&p,&m);
    for(int i = 0;i <= m;i++) scanf("%lld",&a[i]);
    strling[0][0] =  1;
    for(int i = 1;i <= m;i++)
	for(int j = 1;j <= i;j++)
	    strling[i][j] = (strling[i-1][j-1] + strling[i-1][j] * j %p)%p;
    //斯特林数
    ll ans = 0;
    for(int i = 0;i <= m;i++){
		ll tot = 0;
		ll fac = 1ll,xj = 1ll;
		x1j[i] = qp(x+1,n-i);
		for(int j = i-1;j >= 0;j--){
		    x1j[j] = x1j[j+1] * (x+1) % p;
		}//循环时处理 (x+1)^{n-j},n * (n-1) *...*(n-j+1)
		for(int j = 0;j <= i;j++){
		    tot = (tot + strling[i][j] * fac % p* xj % p*x1j[j]%p)%p;
		    fac = fac * (n-j)%p;
		    xj = xj*x%p;
		}
		tot = tot * a[i]%p;
		ans = (ans + tot)%p;
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2020-10-15 22:19  Werner_Yin  阅读(112)  评论(1编辑  收藏  举报