【2020省选Day1T2】 LOJ3300 「联合省选 2020 A」组合数问题

题目链接

解法一:递推求导,搞定k^i

前置知识

求导法则

基本法则:\((x^k)'=kx^{k-1}\)

四则运算:

  • \((f(x)+g(x))'=f'(x)+g'(x)\)
  • \((f(x)\cdot g(x))'=f'(x)g(x)+f(x)g'(x)\)

用乘法法则,可以推出一个常数乘以一个函数的求导法则,即:\((c\cdot f(x))'=0\cdot f(x)+c\cdot f'(x)=c\cdot f'(x)\)。然后,对于减法,就可以看做加\(-1\cdot g(x)\),直接套用加法法则即可得到:\((f(x)-g(x))'=f'(x)-g'(x)\)

复合函数:\((f(g(x)))'=f'(g(x))\cdot g'(x)\)

对于除法,可以看做乘以\(\frac{1}{g(x)}\),也就是\(h(x)=x^{-1}\)\(g(x)\)的复合函数。于是得到:\(\displaystyle \left(\frac{f(x)}{g(x)}\right)'=f'(x)\cdot\frac{1}{g(x)}+f(x)\cdot(-g^{-2}(x)\cdot g'(x))=\frac{f'(x)g(x)-f(x)g'(x)}{g^2(x)}\)

应用:对一个多项式函数,\(F(x)=\sum_{i=0}^{n}f_ix^i\)求导,可以结合“基本法则”和“加法法则”,得到:\(F'(x)=\sum_{i=0}^{n}f_{i}\cdot i\cdot x^{i-1}=\sum_{i=0}^{n-1}f_{i+1}(i+1)x^i\)

二项式定理

\[(x+y)^{n}=\sum_{i=0}^{n}{n\choose i}x^iy^{n-i} \]

证明略。

本题用到它的一种特殊形式,就是\((x+1)^{n}=\sum_{i=0}^{n}{n\choose i}x^i\)。你可以理解为\(y=1\)的情况。

题解

考虑\(m=0\)的subtask,答案就是:\(a_0\cdot \sum_{k=0}^{n}x^k{n\choose k}\)。容易发现这就是上面讲的“二项式定理的特殊形式”,所以它等于\(a_0\cdot(x+1)^n\)

这给我们一个启发,就是可以把\(f(k)\)的每一项拿出来分别计算。于是原式就等于:

\[\sum_{i=0}^{m}a_i\sum_{k=0}^{n}k^ix^k{n\choose k} \]

我们设\(F_i(x)=\sum_{k=0}^{n}k^ix^k{n\choose k}\)。那么原式就等于\(\sum_{i=0}^{m}a_iF_i(x)\)。发现\(F_i(x)\)里,比较令人头疼的,就是这个\(k^i\)。怎么处理它呢?本节介绍的“求导”,就是一种很好的方法。

对一个多项式函数,\(T(x)=\sum_{k=0}^{n}t_kx^k\),考虑把它每一项分别都乘以\(k\),得到\(\text{newT}(x)=\sum_{k=0}^{n}t_k\cdot k\cdot x^k\)。发现这个是什么呢?这个就是把\(T'(x)\)每项都乘以\(x\)。也就是说:\(\text{newT}(x)=x\cdot T'(x)\)

回到本题,我们知道,\(F_i(x)\),就相当于把\(F_{i-1}(x)\)的每一项分别都乘以\(k\)。所以,\(F_i(x)=x\cdot F_{i-1}'(x)\)。也就是先求导,再乘以\(x\)。例如,\(F_0(x)=(x+1)^{n}\),那么就有:\(F_1(x)=x((x+1)^n)'\)。带到原式里,也可以验证出来(把每一项都提一个\(x\)出来即可)。

那么我们就可以依次递推\(F_1(x),F_2(x),\dots ,F_m(x)\)。也就是先对\(F_{i-1}(x)\)求导,再把所有项都乘以\(x\)。例如我多写几个:

  • \(F_0(x)=(x+1)^{n}\)
  • \(F_1(x)=x((x+1)^n)'=n\cdot x\cdot (x+1)^{n-1}\)
  • \(F_2(x)=x(n\cdot x\cdot (x+1)^{n-1})'=n\cdot x\cdot (x+1)^{n-1}+n(n-1)\cdot x^2\cdot (x+1)^{n-2}\)
  • $\dots $

你可以发现,它有很多项。每一项,都可以写成\(d_j\cdot x^{j}\cdot (x+1)^{n-j}\)的形式。其中,\(d_j\)是一个常数。\(0\leq j\leq m\)。更具体来说:

\[F_i(x)=\sum_{j=0}^{m}d_{i,j}x^j(x+1)^{n-j} \]

这个\(d\)数组,我们递推一下,可以\(O(m^2)\)求出。然后预处理一下\(x\)\((x+1)\)的次幂,就可以直接计算答案了。

\(d\)数组怎么递推?首先根据导数的加法法则,我们要对\(F_{i-1}(x)\)求导,就先对它的每一项求导,再加起来。考虑某一项\(d_{i-1,j}x^{j}(x+1)^{n-j}\),它是一个乘法(前面的常数不算,是\(x^j\)\((x+1)^{n-j}\)两个东西相乘)。要对它求导,就用到导数的乘法法则:\((f(x)\cdot g(x))'=f'(x)g(x)+f(x)g'(x)\)。所以\(F_{i-1}(x)\)里的每一项,会对\(F_{i}(x)\)里的两项有贡献。当然,别忘了对\(F_{i-1}(x)\)求导后,还要把结果再乘以\(x\)。具体地也可以写成:

\[d_{i,j}=d_{i-1,j}\cdot j+d_{i-1,j-1}\cdot(n-(j-1)) \]

时间复杂度\(O(m^2)\)

参考代码(在LOJ查看):

//problem:LOJ3300
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXM=1000;
int MOD;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int n,x,m,a[MAXM+5],d[MAXM+5][MAXM+5],pow1[MAXM+5],pow2[MAXM+5];

int main() {
	cin>>n>>x>>MOD>>m;x%=MOD;
	for(int i=0;i<=m;++i)cin>>a[i],a[i]%=MOD;
	d[0][0]=1;
	for(int i=1;i<=m;++i){
		for(int j=0;j<=m;++j){
			// d[i][j] * x^{j} * (x+1)^{n-j}
			d[i][j]=(ll)d[i-1][j]*j%MOD;
			if(j>0)add(d[i][j],(ll)d[i-1][j-1]*(n-(j-1))%MOD);
		}
	}
	pow1[0]=pow_mod(x+1,n-m);
	for(int i=1;i<=m;++i)pow1[i]=(ll)pow1[i-1]*(x+1)%MOD;
	pow2[0]=1;
	for(int i=1;i<=m;++i)pow2[i]=(ll)pow2[i-1]*x%MOD;
	
	int ans=0;
	for(int i=0;i<=m;++i){
		for(int j=0;j<=m;++j){
			add(ans,(ll)a[i]*d[i][j]%MOD*pow2[j]%MOD*pow1[m-j]%MOD);
		}
	}
	cout<<ans<<endl;
	return 0;
}

解法二:用组合恒等式,拆出斯特林数

前置知识

一个组合恒等式

\[k\times {n\choose k}=n\times {n-1\choose k-1} \]

证明:

把两边按定义展开:

左边\(\displaystyle=k\cdot \frac{n!}{k!(n-k)!}\)

右边\(\displaystyle=n\cdot \frac{(n-1)!}{(k-1)!(n-k)!}\)

两边同时乘以\(\frac{1}{k}\),都等于\(\frac{n!}{k!(n-k)!}\)

所以左边\(=\)右边。

下降幂的定义

\[a^{\underline{b}}=a(a-1)\dots (a-b+1) \]

题解

根据上一种方法的初步转化,原式可以写成:

\[\sum_{i=0}^{m}a_i\sum_{k=0}^{n}k^ix^k{n\choose k} \]

\(F_i(x)=\sum_{k=0}^{n}k^ix^k{n\choose k}\)。那么原式就等于\(\sum_{i=0}^{m}a_iF_i(x)\)。还是考虑求\(F_i(x)\)。我们的重点是要把\(k^i\)消掉,剩余的部分,如果是一个\(x^k\)再乘一个组合数,可以直接套二项式定理。因为有了前面的那个组合恒等式,所以现在思路比较明确:先保留\(x^k\)不要动,拿组合数去和\(k^i\)消,消出一个新的组合数加一个系数,我们就赢了。

我们先考虑\(i\geq1\)的情况。

\(i=1\)时,用我们前面写的组合恒等式,可以写成:

\[\begin{align} F_1(x)&=\sum_{k=0}^{n}k\cdot x^k\cdot {n\choose k}\\ &=\sum_{k=0}^{n}x^k\cdot n\cdot{n-1\choose k-1} \end{align} \]

\(i=2\)时,也可以推:

\[\begin{align} F_2(x)&=\sum_{k=0}^{n}k^2\cdot x^k\cdot {n\choose k}\\ &=\sum_{k=0}^{n}x^k\cdot k\cdot n\cdot{n-1\choose k-1}\\ &=\sum_{k=0}^{n}x^k\cdot (1+(k-1))\cdot n\cdot{n-1\choose k-1}\\ &=\sum_{k=0}^{n}x^k\left(n{n-1\choose k-1}+(k-1)\cdot n{n-1\choose k-1}\right)\\ &=\sum_{k=0}^{n}x^k\left(n{n-1\choose k-1}+n(n-1){n-2\choose k-2}\right) \end{align} \]

其中,最重要的一步,是我们把一个\(k\)拆成了\((1+(k-1))\)。这很巧妙。其他的地方都是在套那个组合恒等式。

看最后推出的结果,发现,每个组合数前,是一个\(n\)的下降幂。所以我们盲猜:\(F_i(x)=\sum_{k=0}^{n}x^k\sum_{j=1}^{i}n^{\underline{j}}{n-j\choose k-j}\)

很遗憾,这个猜想不对。

事实上,在\(n^{\underline{j}}{n-j\choose k-j}\)前,还需要加一些系数。只不过对于\(F_1(x)\)\(F_2(x)\)来说,这个系数恰好都是\(1\)

我们继续考虑\(i=3\)的情况。

发现没有,现在,后面的系数变成了\(1\ 3 \ 1\)。不全是\(1\)了。

容易发现,\(F_i\)后面的这一坨东西,恰好有\(i\)项。如果设第\(j\)项前面的系数为\(s(i,j)\),则可以写成:

\[F_i(x)=\sum_{k=0}^{n}x^k\sum_{j=1}^{i}s_{i,j}\cdot n^{\underline{j}}{n-j\choose k-j} \]

考虑如何求这个\(s_{i,j}\)。首先,\(F_{i-1}(x)\)后面的东西里,每一项都要乘以\(k\)。例如,\(s_{i-1,j}\cdot n^{\underline{j}}{n-j\choose k-j}\),乘以\(k\),变成\(k\cdot s_{i-1,j}\cdot n^{\underline{j}}{n-j\choose k-j}\)。然后,我们会把\(k\),拆成\((j+(k-j))\)。所以\(s_{i-1,j}\)会对\(s_{i,?}\)里的两个地方产生贡献。一个是\(j\)产生的,贡献到\(s_{i,j}\),贡献系数为\(j\)。另一个是\((k-j)\)产生的,贡献到\(s_{i,j+1}\),贡献系数为\(1\)

那反过来说,就可以得到:

\[s_{i,j}=s_{i-1,j}\cdot j+s_{i-1,j-1} \]

其中,边界是\(s_{1,1}=1\)

于是就可以\(O(m^2)\)递推求出整个\(s\)数组。顺便说一句,这里的\(s\),其实就是第二类斯特林数,不过有没有看出来都不影响解题。

现在求出了\(s\),再回头看\(F_i(x)\)。我们按一开始说的思路,用二项式定理,把\(x^k\)和组合数搞到一起去。具体来说:

\[\begin{align} F_i(x)&=\sum_{j=1}^{i}s_{i,j}\cdot n^{\underline{j}}\sum_{k=0}^{n}x^k{n-j\choose k-j}\\ &=\sum_{j=1}^{i}s_{i,j}\cdot n^{\underline{j}}\sum_{k=0}^{n-j}x^{k+j}{n-j\choose k}\\ &=\sum_{j=1}^{i}s_{i,j}\cdot n^{\underline{j}}\cdot x^j\sum_{k=0}^{n-j}x^k{n-j\choose k}\\ &=\sum_{j=1}^{i}s_{i,j}\cdot n^{\underline{j}}\cdot x^j(x+1)^{n-j}\\ \end{align} \]

以上是\(i\geq 1\)的情况。当\(i=0\)时,显然有\(F_0=(x+1)^n\)。当然,你也可以认为,\(s_{0,0}=1\)\(n^{\underline{0}}=1\)\(s_{i,0}=0\) (\(i\geq 1\))。这样,你把\(j\)改成从\(0\)开始循环,也是对的。这样就不用特判\(i=0\)的情况。

然后答案就是\(\sum_{i=0}^{m}a_iF_i(x)\)

时间复杂度\(O(m^2)\)

参考代码,他鸽了

两种解法的比较与联系

事实上,推到最后,大家都能看出来,第一种解法里的\(d_{i,j}\),就是第二种解法里的\(s_{i,j}\cdot n^{\underline{j}}\)

两种解法,都想消掉\(k^i\)。他们走的路径不同。

  • 第一种解法,比较鲁莽。你不是有一个\(k^i\)吗,我对着你硬消。求一次导干掉一个\(k\)。这种“鲁莽”的背后,是有“求导”这个强大的工具在支持。
  • 第二种解法,看起来稍微圆滑一点。我先让组合数\({n\choose k}\)去和你消。用的就是一个组合恒等式。然后等你\(k^i\)没了,我再把剩下的组合数和\(x^k\)打包成一个二项式定理。

猜你喜欢

转载自www.cnblogs.com/dysyn1314/p/13196203.html