斯特林的试炼

下面提到的内容均属组合数学范畴。


前言

由于式子较多,为增强直观性,笔者将一改以往的笔风,用 ( n m ) \displaystyle\binom{n}{m} (mn) 表示组合数 C ⁡ n m \operatorname{C}_n^m Cnm。同理,笔者分别会用 [ n k ] \begin{bmatrix}n \\ k\end{bmatrix} [nk] { n k } \begin{Bmatrix}n \\ k\end{Bmatrix} {nk} 表示第一类斯特林数 s ( n , k ) s(n, k) s(n,k) 和第二类斯特林数 S ( n , k ) S(n, k) S(n,k)

阅读这篇文章前,您需要较为熟练地掌握组合数、排列数(特别是圆排列)的定义和公式,以及常见的组合恒等式。


前置知识

上升幂和下降幂

  • 定义 x n ‾ x^{\overline{n}} xn x x x n n n上升阶乘幂,又称上升幂。它的值等于 ∏ i = 0 n ( x + i ) \prod_{i = 0}^n (x + i) i=0n(x+i)
  • 定义 x n ‾ x^{\underline{n}} xn x x x n n n下降阶乘幂,又称下降幂。它的值等于 ∏ i = 0 n ( x − i ) \prod_{i = 0}^n (x - i) i=0n(xi)

上升幂和下降幂的转换公式(请读者自主完成证明):
( − x ) n ‾ = ( − 1 ) n x n ‾ ( − x ) n ‾ = ( − 1 ) n x n ‾ (-x)^{\overline{n}} = (-1)^n x^{\underline{n}} \\ (-x)^{\underline{n}} = (-1)^n x^{\overline{n}} (x)n=(1)nxn(x)n=(1)nxn
实际上,上升幂和下降幂与普通幂之间也有转换公式,不过由于公式中存在斯特林数,因此将在下文提及。

普通生成函数

通俗的讲,对于一个数列 a n a_n an,它的普通生成函数(简称 OGF ⁡ \operatorname{OGF} OGF)为一个多项式:
f ( x ) = ∑ i = 0 ∞ a i x i f(x) = \sum_{i = 0}^{\infin} a_ix^i f(x)=i=0aixi
(严谨地讲, f ( x ) f(x) f(x) 应该叫做形式幂级数,不过笔者并不了解其确切定义,因此这里用更通俗的方式解释。)

也就是说,对于多项式 f ( x ) f(x) f(x),有 a n = [ x n ] f ( x ) a_n = [x^n]f(x) an=[xn]f(x)

如果我们能够得到这个多项式,那么我们也可以从它的系数中获取到这个序列

同时,如果我们要对数列间做一些运算(如卷积),也可以通过对这个多项式做运算得到最终的结果。


第一类斯特林数

n n n 个互不相同的数分为 k k k圆排列,这样的方案数称为第一类斯特林数,也叫斯特林轮换数,记做 s ( n , k ) s(n, k) s(n,k),也记做 [ n k ] \begin{bmatrix}n \\ k\end{bmatrix} [nk]

递推式

我们知道,如果在一个长为 n n n 的圆排列中插入一个数(一共有 n n n 个位置可以插入),形成的 n n n 种长为 n + 1 n + 1 n+1 的圆排列是互不相同的,因为它们互相之间不可以通过轮换得到

所以,第一类斯特林数有递推公式:
[ n k ] = [ n − 1 k − 1 ] + ( n − 1 ) [ n − 1 k ] \begin{bmatrix}n \\ k\end{bmatrix} = \begin{bmatrix}n - 1 \\ k - 1\end{bmatrix} + (n - 1) \begin{bmatrix}n - 1 \\ k\end{bmatrix} [nk]=[n1k1]+(n1)[n1k]
考虑从组合意义去证明:加入一个数,要么自己单独构成一个圆排列,要么插入已有的圆排列。前面说过,后者的方案数为 n − 1 n - 1 n1。所以,根据加法原理,即可得到该递推式。

当然,该递推式也存在边界条件: [ n 0 ] = [ n = 0 ] \begin{bmatrix}n \\ 0\end{bmatrix} = [n = 0] [n0]=[n=0]

由于第一类斯特林数不存在通项公式,这个递推式就显得尤为重要了,它将辅助证明下面的诸多结论。

常用公式

1. 和阶乘的关系:

n ! = ∑ i = 0 n [ n i ] n! = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} n!=i=0n[ni]

前者实际上是指 1 ∼ n 1\sim n 1n 的全排列。由于每一种排列中,圆排列的个数是一定的,那么我们可以考虑枚举所有的圆排列个数,把对应的第一类斯特林数相加即为答案。

2. 和上升幂的关系(上升幂转普通幂):

x n ‾ = ∑ i = 0 n [ n i ] x i x^{\overline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} x^i xn=i=0n[ni]xi

证明:

  1. 考虑构造生成函数 f n ( x ) = ∑ i = 0 ∞ [ n i ] x i f_n(x) = \sum_{i = 0}^\infin \begin{bmatrix}n \\ i\end{bmatrix} x^i fn(x)=i=0[ni]xi.

    ∵ \because 根据递推式有 f n ( x ) = x f n − 1 ( x ) + ( n − 1 ) f n − 1 ( x ) f_n(x) = xf_{n - 1}(x) + (n - 1)f_{n - 1}(x) fn(x)=xfn1(x)+(n1)fn1(x).

    ∴ \therefore f n ( x ) = ( x + n − 1 ) f n − 1 ( x ) f_n(x) = (x + n - 1)f_{n - 1}(x) fn(x)=(x+n1)fn1(x).

    ∵ \because n = 1 n = 1 n=1 时,有 f 1 ( x ) = x f_1(x) = x f1(x)=x.

    ∴ \therefore f n ( x ) = ∑ i = 0 n [ n i ] x i = x n ‾ f_n(x) = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} x^i = x^{\overline{n}} fn(x)=i=0n[ni]xi=xn.

    以上证明过程也说明了:所有 n n n 相同的第一类斯特林数(也叫第 n n n 行第一类斯特林数)的生成函数是 x n ‾ x^{\overline{n}} xn

  2. 受上面过程的启发,我们还可以考虑用数学归纳法证明。

    显然当 n = 0 n = 0 n=0 n = 1 n = 1 n=1 时,原式左右两边相等。

    设当前结论满足 n = m n = m n=m 的情况,求证当前结论也满足 n = m + 1 n = m + 1 n=m+1 的情况。

    即证明: ∑ i = 0 n + 1 [ n + 1 i ] x i = ( x + n ) ∑ i = 0 n [ n i ] x i \sum_{i = 0}^{n + 1} \begin{bmatrix}n + 1 \\ i\end{bmatrix} x^i = (x + n) \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} x^i i=0n+1[n+1i]xi=(x+n)i=0n[ni]xi
    ∑ i = 0 n + 1 [ n + 1 i ] x i = ∑ i = 0 n + 1 ( [ n i − 1 ] + n ⋅ [ n i ] ) x i = ∑ i = 0 n + 1 [ n i − 1 ] x i + ∑ i = 0 n + 1 n ⋅ [ n i ] x i = ∑ i = 0 n [ n i ] x i + 1 + n ⋅ ∑ i = 0 n [ n i ] x i = x ⋅ ∑ i = 0 n [ n i ] x i + n ⋅ ∑ i = 0 n [ n i ] x i = ( x + n ) ∑ i = 0 n [ n i ] x i \begin{aligned} &\sum_{i = 0}^{n + 1} \begin{bmatrix}n + 1 \\ i\end{bmatrix} x^i \\ = &\sum_{i = 0}^{n + 1} (\begin{bmatrix}n \\ i - 1\end{bmatrix} + n\cdot\begin{bmatrix}n \\ i\end{bmatrix}) x^i \\ = &\sum_{i = 0}^{n + 1} \begin{bmatrix}n \\ i - 1\end{bmatrix} x^i + \sum_{i = 0}^{n + 1} n\cdot\begin{bmatrix}n \\ i\end{bmatrix} x^i \\ = &\sum_{i = 0}^{n} \begin{bmatrix}n \\ i\end{bmatrix} x^{i + 1} + n \cdot\sum_{i = 0}^{n}\begin{bmatrix}n \\ i\end{bmatrix} x^i \\ = &x\cdot\sum_{i = 0}^{n} \begin{bmatrix}n \\ i\end{bmatrix} x^{i} + n \cdot\sum_{i = 0}^{n}\begin{bmatrix}n \\ i\end{bmatrix} x^i \\ = &(x + n) \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} x^i \end{aligned} =====i=0n+1[n+1i]xii=0n+1([ni1]+n[ni])xii=0n+1[ni1]xi+i=0n+1n[ni]xii=0n[ni]xi+1+ni=0n[ni]xixi=0n[ni]xi+ni=0n[ni]xi(x+n)i=0n[ni]xi

同一行第一类斯特林数的求法

模板题

根据递推公式,我们可以得到一个 O ( n 2 ) \mathcal{O}(n^2) O(n2) 的做法。

根据上面的结论(第 n n n 行第一类斯特林数的生成函数是 x n ‾ x^{\overline{n}} xn),我们有 O ( n log ⁡ 2 2 n ) \mathcal{O}(n \log_2^2 n) O(nlog22n) 的做法:

因为上升幂本质上是若干个二项式相乘,所以我们分治后直接跑 NTT ⁡ \operatorname{NTT} NTT 即可。

有没有更快的做法?当然有!

考虑倍增(它是多项式内容里的常客),当前求出了 x n ‾ x^{\overline{n}} xn,要求 x 2 n ‾ x^{\overline{2n}} x2n

因为 x 2 n ‾ = x n ‾ ( x + n ) n ‾ x^{\overline{2n}} = x^{\overline{n}} (x + n)^{\overline{n}} x2n=xn(x+n)n,所以我们只要能够求出 ( x + n ) n ‾ (x + n)^{\overline{n}} (x+n)n,就能够做到 O ( n log ⁡ 2 n ) \mathcal{O}(n \log_2 n) O(nlog2n) 了。

考虑生成函数的展开形式:已知 f ( x ) = ∑ i = 0 n a i x i f(x) = \sum_{i = 0}^n a_ix^i f(x)=i=0naixi,要求 f ( x + n ) = ∑ i = 0 n a i ( x + n ) i f(x + n) = \sum_{i = 0}^n a_i(x + n)^i f(x+n)=i=0nai(x+n)i

我们对后面的这个式子做二项式展开:
∑ i = 0 n a i ( x + n ) i = ∑ i = 0 n a i ∑ j = 0 i ( i j ) x j n i − j = ∑ i = 0 n a i ∑ j = 0 i i ! j ! ( i − j ) ! x j n i − j = ∑ j = 0 n x j j ! ∑ i = j n a i i ! n i − j ( i − j ) ! = ∑ j = 0 n x j j ! ∑ i = 0 n − j a i + j ( i + j ) ! n i i ! \begin{aligned} &\sum_{i = 0}^n a_i(x + n)^i \\ = &\sum_{i = 0}^n a_i\sum_{j = 0}^i\displaystyle\binom{i}{j}x^jn^{i - j} \\ = &\sum_{i = 0}^n a_i\sum_{j = 0}^i\frac{i!}{j!(i - j)!}x^jn^{i - j} \\ = &\sum_{j = 0}^n\frac{x_j}{j!}\sum_{i = j}^n a_ii!\frac{n^{i - j}}{(i - j)!} \\ = &\sum_{j = 0}^n\frac{x_j}{j!}\sum_{i = 0}^{n - j} a_{i + j}(i + j)!\frac{n^{i}}{i!} \end{aligned} ====i=0nai(x+n)ii=0naij=0i(ji)xjniji=0naij=0ij!(ij)!i!xjnijj=0nj!xji=jnaii!(ij)!nijj=0nj!xji=0njai+j(i+j)!i!ni
不看左边,我们设右边的式子为 c j c_j cj,发现其很像卷积形式,但是用 NTT ⁡ \operatorname{NTT} NTT 是不能直接做的。

没关系,我们还是可以用 NTT ⁡ \operatorname{NTT} NTT 做的,只不过有一个小技巧:颠倒系数

什么意思?

正常的卷积形式是 ∑ i = 0 j f i g j − i \sum_{i = 0}^j f_i g_{j - i} i=0jfigji

如果我们把 f i f_i fi 这个数列翻转过来,还是做正常的卷积,得到的结果应该是: ∑ i = 0 j f n − i g j − i \sum_{i = 0}^jf_{n - i} g_{j - i} i=0jfnigji

或者把 g i g_i gi 这个数列翻转过来,也做正常的卷积,得到的结果是: ∑ i = 0 j f i g n − j + i \sum_{i = 0}^jf_i g_{n - j + i} i=0jfignj+i

可以发现,在原来的卷积中, f f f 的下标里的 i i i g g g 的下标里的 i i i 是以相反数的形式出现的。不过在颠倒系数后, f f f 的下标里的 i i i g g g 的下标里的 i i i 是以同符号形式出现的。

我们再看看 c j c_j cj,如果设 f i = a i i ! f_i = a_i i! fi=aii!,设 g i = n i i ! g_i = \frac{n^i}{i!} gi=i!ni,那么有 c j = ∑ i = 0 n − j f i + j g i c_j = \sum_{i = 0}^{n - j} f_{i+j} g_{i} cj=i=0njfi+jgi,下标中的 i i i 也是以同符号形式出现的!

所以,算法的大体框架已经出来了:我们只要求出 f i f_i fi g i g_i gi,然后颠倒其中的一个,再把它们做一遍 NTT ⁡ \operatorname{NTT} NTT

当然,做完 NTT ⁡ \operatorname{NTT} NTT 后的答案并不是我们想要的答案,只不过是形式对应了而已。

如果将 f i f_i fi 颠倒,即 f i ′ = f n − i f'_i = f_{n - i} fi=fni,则对 f ′ f' f g g g 卷积后的结果为:

c j ′ = ∑ i = 0 j f n − j + i g j c'_j = \sum_{i = 0}^jf_{n - j + i} g_{j} cj=i=0jfnj+igj

那么有对应关系:

c j = ∑ i = 0 n − j f i + j g i = ∑ i = 0 n − j f n − ( n − j ) + i g i = c n − j ′ \begin{aligned} c_j&=\sum_{i = 0}^{n - j} f_{i+j} g_{i} \\ &=\sum_{i = 0}^{n - j} f_{n - (n - j) +i} g_{i} \\ &= c'_{n - j} \end{aligned} cj=i=0njfi+jgi=i=0njfn(nj)+igi=cnj

所以只要再把求出的 c ′ c' c 颠倒即为答案。

如果将 g i g_i gi 颠倒,即 g i ′ = g n − i g'_i = g_{n - i} gi=gni,则对 f f f g ′ g' g 卷积后的结果为:

c j ′ = ∑ i = 0 j f i g n − j + i c'_j = \sum_{i = 0}^jf_{i} g_{n - j + i} cj=i=0jfignj+i

那么有对应关系:

c j = ∑ i = 0 n − j f i + j g i = ∑ i = j n f i g i − j = ∑ i = 0 n + j f i g n − ( n + j ) + i = c j + n ′ \begin{aligned} c_j &= \sum_{i = 0}^{n - j} f_{i+j} g_{i} \\ &= \sum_{i = j}^nf_ig_{i - j} \\ &= \sum_{i = 0}^{n + j} f_ig_{n - (n + j) + i} \\ &= c'_{j + n} \end{aligned} cj=i=0njfi+jgi=i=jnfigij=i=0n+jfign(n+j)+i=cj+n

(注意:由于 f f f g g g 都只有第 0 ∼ n 0 \sim n 0n 位不为 0 0 0,所以推导的第三步加入的所有贡献均为 0 0 0。)

所以只要把求出的 c ′ c' c 整体向低位移动 n n n 位即为答案。

求出 ( x + n ) n ‾ (x + n)^{\overline{n}} (x+n)n 后,我们再将其和 x n ‾ x^{\overline{n}} xn 做卷积即可。

注意要特判奇数的情况,此时还要再乘上一个 x + 2 n x + 2n x+2n,直接 O ( n ) \mathcal{O}(n) O(n) 乘即可。

代码(写的是第一种,即颠倒 f i f_i fi):

#include
#include
#include
#include

#define MAXN 2000005

#define MOD 167772161
#define Min_G 3
#define Inv_Min_G 55924054

using namespace std;

int n, fac[MAXN], inv[MAXN], f[MAXN], g[MAXN], s[MAXN], t[MAXN];

inline int Pow(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;
}

namespace Polynomial
{
	int p, q, rev[MAXN];

	inline void Init(int n)
	{
		p = 1, q = -1;
		while(p < n) p <<= 1, ++q;
		for(register int i = 0; i < p; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << q);
		return;
	}

	inline void NTT(int *f, int n, bool Inverse = false)
	{
		for(register int i = 0; i < n; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
		for(register int len = 2; len <= n; len <<= 1)
		{
			int k = (len >> 1), g = Pow(Inverse ? Inv_Min_G : Min_G, (MOD - 1) / len);
			for(register int i = 0; i < n; i += len)
			{
				int w = 1;
				for(register int j = 0; j < k; j++)
				{
					int u = f[i + j], v = 1LL * w * f[i + j + k] % MOD;
					f[i + j] = (u + v) % MOD;
					f[i + j + k] = (u - v + MOD) % MOD;
					w = 1LL * w * g % MOD;
				}
			}
		}
		if(Inverse)
		{
			int Inv_N = Pow(n, MOD - 2);
			for(register int i = 0; i < n; i++) f[i] = 1LL * f[i] * Inv_N % MOD;
		}
		return;
	}
}

inline void Init(int n)
{
	fac[0] = 1;
	for(register int i = 1; i <= n; i++) fac[i] = 1LL * fac[i - 1] * i % MOD;
	inv[n] = Pow(fac[n], MOD - 2);
	for(register int i = n - 1; i >= 0; i--) inv[i] = 1LL * inv[i + 1] * (i + 1) % MOD;
	return;
}

inline void Solve(int *f, int n)
{
	if(n == 0) return f[0] = 1, void();
	if(n & 1)
	{
		Solve(f, n - 1);
		if(n & 1)
		{
			for(register int i = n; i >= 1; i--) f[i] = (1LL * f[i] * (n - 1) % MOD + f[i - 1]) % MOD;
			f[0] = 1LL * f[0] * (n - 1) % MOD;
		}
		return;
	}
	Solve(f, n >> 1);

	int m = (n >> 1), w = 1;
	Polynomial :: Init((m << 1) + 1);
	int p = Polynomial :: p;
	fill(s, s + p, 0), fill(t, t + p, 0);
	for(register int i = 0; i <= m; i++)
	{
		s[i] = 1LL * f[m - i] * fac[m - i] % MOD;
		t[i] = 1LL * w * inv[i] % MOD;
		w = 1LL * w * m % MOD;
	}
	Polynomial :: NTT(s, p), Polynomial :: NTT(t, p);
	for(register int i = 0; i < p; i++) s[i] = 1LL * s[i] * t[i] % MOD;
	Polynomial :: NTT(s, p, true);

	fill(f + n + 1, f + p, 0), fill(g, g + p, 0);
	for(register int i = 0; i <= m; i++) g[i] = 1LL * s[m - i] * inv[i] % MOD;
	Polynomial :: NTT(f, p), Polynomial :: NTT(g, p);
	for(register int i = 0; i < p; i++) f[i] = 1LL * f[i] * g[i] % MOD;
	Polynomial :: NTT(f, p, true);
	return;
}

int main()
{
	scanf("%d", &n);
	Init(n << 1);
	Solve(f, n);
	for(register int i = 0; i <= n; i++) printf("%d ", f[i]);
	putchar('\n');
	return 0;
}

例题

  • [FJOI2016]建筑师

题解:

做组合数学的题,光有推式子能力还不行,还得通过组合意义列出答案的式子。

例如这道题目,我们要思考的是,如何在其中加入组合意义。

可以发现,无论是从左边还是从右边,都可以并且一定会看到最高的楼。

然后,我们如果设 ∗ * 为能够看到的楼, . . . 为不能够看到的楼, H H H 为最高的楼,那么建筑一定是这种形式:
∗ . . . . ∗ . . ∗ . . ∗ . . . H . ∗ . . . . . ∗ . . ∗ *....*..*..*...H.*.....*..* ...........H........
我们来从左侧的角度解释一下这个图的一些细节(右侧同理):

  • H H H 左侧的 ∗ * 数量为 A − 1 A - 1 A1
  • 左侧要么不存在 ∗ * ,要么第一个字符即为 ∗ * ,每一个 ∗ * 的右边存在若干个 . . .,这个 ∗ * 的高度高于这些 . . .
  • 对于左侧的一个 ∗ * ,右侧的 . . . 顺序随意

“顺序随意” 给了我们一定的启发,如果耐心把式子列出来,可以发现:如果我们把一个 ∗ * 和其右侧的 m m m . . . 看作一个整体,那么由于一共有 ( m − 1 ) ! (m - 1)! (m1)! 种排列方式。

如果你对排列数十分敏感,可以一眼看出——这就是圆排列。

圆排列!我们能想到什么?第一类斯特林数!

所以,我们只要在 n − 1 n - 1 n1 个数中选出 A + B − 2 A + B - 2 A+B2 个圆排列,并任选 A − 1 A-1 A1 个放在 H H H 的左边, B − 1 B - 1 B1 个放在 H H H 的右边,即为一种方案。

那么,答案就呼之欲出了: [ n − 1 A + B − 2 ] ⋅ ( A + B − 2 A − 1 ) \begin{bmatrix}n - 1 \\ A + B - 2\end{bmatrix} \cdot \displaystyle\binom{A + B - 2}{A - 1} [n1A+B2](A1A+B2)

直接预处理即可,时间在可承受范围内。

代码就不贴了。


第二类斯特林数

n n n 个互不相同的数划分为 k k k互不区分、互不相交的集合,这样的方案数称为第二类斯特林数,记做 S ( n , k ) S(n, k) S(n,k),也记做 { n k } \begin{Bmatrix}n \\ k\end{Bmatrix} {nk}

第二类斯特林数相对于第一类的用处会更加广泛。


递推式

{ n k } = { n − 1 k − 1 } + k ⋅ { n − 1 k } \begin{Bmatrix}n \\ k\end{Bmatrix} = \begin{Bmatrix}n - 1 \\ k - 1\end{Bmatrix} + k\cdot \begin{Bmatrix}n - 1 \\ k\end{Bmatrix} {nk}={n1k1}+k{n1k}

还是考虑从组合意义去证明:加入一个数,要么自己单独构成一个集合,要么加入已有的 k k k 个集合。

边界条件同样是: { n 0 } = [ n = 0 ] \begin{Bmatrix}n \\ 0\end{Bmatrix} = [n = 0] {n0}=[n=0]

通项公式

第二类斯特林数是存在通项公式的:
{ n k } = 1 k ! ∑ i = 0 k ( − 1 ) i ( k i ) ( k − i ) n = ∑ i = 0 k ( − 1 ) i i ! ⋅ ( k − i ) n ( k − i ) ! = ∑ i = 0 k ( − 1 ) k − i ( k − i ) ! ⋅ i n i ! \begin{Bmatrix}n \\ k\end{Bmatrix} = \frac{1}{k!}\sum_{i = 0}^k(-1)^i\displaystyle\binom{k}{i} (k - i)^n = \sum_{i = 0}^k \frac{(-1)^i}{i!} \cdot \frac{(k - i)^n}{(k - i)!} = \sum_{i = 0}^k\frac{(-1)^{k - i}}{(k - i)!} \cdot \frac{i^n}{i!} {nk}=k!1i=0k(1)i(ik)(ki)n=i=0ki!(1)i(ki)!(ki)n=i=0k(ki)!(1)kii!in
(上面把推式子可能会用到的三种形式都写出来了。)

证明:

n n n 个互不相同的数划分为 k k k 个互不相同的集合的方案数为 f k , g k f_k, g_k fk,gk,前者允许空集,后者不允许空集。

那么有:
f k = k n f k = ∑ i = 0 k ( k i ) g i f_k = k^n \\ f_k = \sum_{i = 0}^k\displaystyle\binom{k}{i} g_i fk=knfk=i=0k(ik)gi
考虑二项式反演:
g k = ∑ i = 0 k ( − 1 ) k − i ( k i ) f i = ∑ i = 0 k ( − 1 ) i ( k i ) ( k − i ) n g_k = \sum_{i = 0}^k (-1)^{k - i}\displaystyle\binom{k}{i} f_i = \sum_{i = 0}^k(-1)^i\displaystyle\binom{k}{i} (k - i)^n gk=i=0k(1)ki(ik)fi=i=0k(1)i(ik)(ki)n
由于 g k g_k gk 在统计过程中,集合之间相互区分,所以:

{ n k } = 1 k ! g k = 1 k ! ∑ i = 0 k ( − 1 ) i ( k i ) ( k − i ) n \begin{Bmatrix}n \\ k\end{Bmatrix} = \frac{1}{k!} g_k = \frac{1}{k!}\sum_{i = 0}^k(-1)^i\displaystyle\binom{k}{i} (k - i)^n {nk}=k!1gk=k!1i=0k(1)i(ik)(ki)n

常用公式

和下降幂的关系(普通幂转下降幂):

x n = ∑ i = 0 x ( x i ) { n i } i ! = ∑ i = 0 n { n i } x i ‾ x^n = \sum_{i = 0}^x \displaystyle\binom{x}{i}\begin{Bmatrix}n \\ i\end{Bmatrix} i! = \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix}x^{\underline{i}} xn=i=0x(ix){ni}i!=i=0n{ni}xi

证明:

考虑组合意义,等式左侧即为 n n n 个互不相同的数划分为 x x x 个互不相同的可空集合的方案数,等式右侧即选出 i i i 个集合强制非空的方案数。

当然,你也可以考虑用上面的数学归纳法证明,这里就留给读者当作练习吧。

同一行第一类斯特林数的求法

据说有一种用 EGF ⁡ \operatorname{EGF} EGF(指数型生成函数)做的方法,但是由于笔者能力有限,加上其速度较慢,故此处不讲解这种方法。

其实非常简单,我们再一次观察通项公式:
{ n k } = ∑ i = 0 k ( − 1 ) i i ! ⋅ ( k − i ) n ( k − i ) ! \begin{Bmatrix}n \\ k\end{Bmatrix} = \sum_{i = 0}^k \frac{(-1)^i}{i!} \cdot \frac{(k - i)^n}{(k - i)!} {nk}=i=0ki!(1)i(ki)!(ki)n
可以发现这就是一个卷积的形式, NTT ⁡ \operatorname{NTT} NTT 即可。

代码:

#include
#include
#include
#include

#define MAXN 1000005

#define MOD 167772161
#define Min_G 3
#define Inv_Min_G 55924054

using namespace std;

int n, f[MAXN], g[MAXN], fac[MAXN], invf[MAXN], p, q, rev[MAXN];

inline int Get_MOD(int k) { if(k >= MOD) k -= MOD; return k; }

inline int Pow(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;
}

inline void Init()
{
	fac[0] = 1;
	for(register int i = 1; i <= n; i++) fac[i] = 1LL * fac[i - 1] * i % MOD;
	invf[n] = Pow(fac[n], MOD - 2);
	for(register int i = n - 1; i >= 0; i--) invf[i] = 1LL * invf[i + 1] * (i + 1) % MOD;
	p = 1, q = -1;
	while(p < (n << 1)) p <<= 1, ++q;
	for(register int i = 0; i < p; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << q);
	return;
}

inline void NTT(int *f, bool Inverse = false)
{
	for(register int i = 0; i < p; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
	for(register int len = 2; len <= p; len <<= 1)
	{
		int k = (len >> 1), g = Pow(Inverse ? Inv_Min_G : Min_G, (MOD - 1) / len);
		for(register int i = 0; i < p; i += len)
		{
			int w = 1;
			for(register int j = 0; j < k; j++)
			{
				int u = f[i + j], v = 1LL * w * f[i + j + k] % MOD;
				f[i + j] = Get_MOD(u + v);
				f[i + j + k] = Get_MOD(u - v + MOD);
				w = 1LL * w * g % MOD;
			}
		}
	}
	if(Inverse)
	{
		int Inv_N = Pow(p, MOD - 2);
		for(register int i = 0; i < p; i++) f[i] = 1LL * f[i] * Inv_N % MOD;
	}
	return;
}

int main()
{
	scanf("%d", &n);
	Init();
	for(register int i = 0; i <= n; i++)
	{
		f[i] = 1LL * ((i & 1) ? MOD - 1 : 1) * invf[i] % MOD;
		g[i] = 1LL * Pow(i, n) * invf[i] % MOD;
	}
	NTT(f), NTT(g);
	for(register int i = 0; i < p; i++) f[i] = 1LL * f[i] * g[i] % MOD;
	NTT(f, true);
	for(register int i = 0; i <= n; i++) printf("%d ", f[i]);
	return 0;
}

例题

  • BZOJ5093 图的价值

“简单无向图” 是指无重边、无自环的无向图(不一定连通)。

一个带标号的图的价值定义为每个点度数的 k k k 次方的和。

给定 n n n k k k,请计算所有 n n n 个点的带标号的简单无向图的价值之和。

答案对 998233353 998233353 998233353 取模。

1 ≤ n ≤ 1 0 9 , 1 ≤ k ≤ 2 × 1 0 5 1 \le n \le 10^9, 1\le k\le 2\times 10^5 1n109,1k2×105

题解:

显然每个点对答案的贡献是相同的,所以我们只需要算出一个点对答案的贡献,最后乘上 n n n 即可。

我们枚举一个点的度数,那么有:
A n s w e r = n ⋅ 2 ( n − 1 2 ) ∑ i = 0 n − 1 i k ( n − 1 i ) Answer = n \cdot 2^{\binom{n - 1}{2}} \sum_{i = 0}^{n - 1}i^k\displaystyle\binom{n - 1}{i} Answer=n2(2n1)i=0n1ik(in1)
这个时候,如果只用一般的方法,大概率会无从下手。

回顾普通幂转化为第二类斯特林数的公式,我们可以得到:
A n s w e r = n ⋅ 2 ( n − 1 2 ) ∑ i = 0 n − 1 i k ( n − 1 i ) = n ⋅ 2 ( n − 1 2 ) ∑ i = 0 n − 1 ( n − 1 i ) ∑ j = 0 min ⁡ ( i , k ) ( i j ) { k j } j ! = n ⋅ 2 ( n − 1 2 ) ∑ j = 0 min ⁡ ( n − 1 , k ) { k j } j ! ∑ i = j n − 1 ( n − 1 i ) ( i j ) = n ⋅ 2 ( n − 1 2 ) ∑ j = 0 min ⁡ ( n − 1 , k ) { k j } j ! ( n − 1 j ) ∑ i = j n − 1 ( n − j − 1 i − j ) = n ⋅ 2 ( n − 1 2 ) ∑ j = 0 min ⁡ ( n − 1 , k ) { k j } j ! ( n − 1 j ) ∑ i = 0 n − j − 1 ( n − j − 1 i ) = n ⋅ 2 ( n − 1 2 ) ∑ j = 0 min ⁡ ( n − 1 , k ) { k j } j ! ( n − 1 j ) 2 n − j − 1 \begin{aligned} Answer &= n \cdot 2^{\binom{n - 1}{2}} \sum_{i = 0}^{n - 1}i^k\displaystyle\binom{n - 1}{i} \\ &= n \cdot 2^{\binom{n - 1}{2}} \sum_{i = 0}^{n - 1}\displaystyle\binom{n - 1}{i} \sum_{j = 0}^{\min(i, k)}\displaystyle\binom{i}{j} \begin{Bmatrix}k \\ j\end{Bmatrix} j! \\ &=n \cdot 2^{\binom{n - 1}{2}} \sum_{j = 0}^{\min(n - 1, k)} \begin{Bmatrix}k \\ j\end{Bmatrix} j! \sum_{i = j}^{n - 1} \displaystyle\binom{n - 1}{i}\displaystyle\binom{i}{j} \\ &= n \cdot 2^{\binom{n - 1}{2}} \sum_{j = 0}^{\min(n - 1, k)} \begin{Bmatrix}k \\ j\end{Bmatrix} j! \displaystyle\binom{n - 1}{j} \sum_{i = j}^{n - 1} \displaystyle\binom{n - j - 1}{i - j} \\ &= n \cdot 2^{\binom{n - 1}{2}} \sum_{j = 0}^{\min(n - 1, k)} \begin{Bmatrix}k \\ j\end{Bmatrix} j! \displaystyle\binom{n - 1}{j} \sum_{i = 0}^{n - j - 1} \displaystyle\binom{n - j - 1}{i} \\ &= n \cdot 2^{\binom{n - 1}{2}} \sum_{j = 0}^{\min(n - 1, k)} \begin{Bmatrix}k \\ j\end{Bmatrix} j! \displaystyle\binom{n - 1}{j} 2^{n - j - 1} \end{aligned} Answer=n2(2n1)i=0n1ik(in1)=n2(2n1)i=0n1(in1)j=0min(i,k)(ji){kj}j!=n2(2n1)j=0min(n1,k){kj}j!i=jn1(in1)(ji)=n2(2n1)j=0min(n1,k){kj}j!(jn1)i=jn1(ijnj1)=n2(2n1)j=0min(n1,k){kj}j!(jn1)i=0nj1(inj1)=n2(2n1)j=0min(n1,k){kj}j!(jn1)2nj1
由于本题没有提交地址,因此笔者偷懒没有写代码,望见谅~

斯特林反演

常用公式

在前面的学习中,我们知道有如下两个等式:
( − x ) n ‾ = ( − 1 ) n x n ‾ x n ‾ = ∑ i = 0 n [ n i ] x i (-x)^{\overline{n}} = (-1)^n x^{\underline{n}} \\ x^{\overline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} x^i (x)n=(1)nxnxn=i=0n[ni]xi
那么有:
( − x ) n ‾ = ∑ i = 0 n [ n i ] ( − x ) i (-x)^{\overline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} (-x)^i (x)n=i=0n[ni](x)i
即:
( − 1 ) n x n ‾ = ∑ i = 0 n [ n i ] ( − 1 ) i x i (-1)^nx^{\underline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} (-1)^ix^i (1)nxn=i=0n[ni](1)ixi
所以有:
x n ‾ = ∑ i = 0 n [ n i ] ( − 1 ) n − i x i x^{\underline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} (-1)^{n - i}x^i xn=i=0n[ni](1)nixi
类似的,我们可以由如下两个公式:
( − x ) n ‾ = ( − 1 ) n x n ‾ x n = ∑ i = 0 n { n i } x i ‾ (-x)^{\underline{n}} = (-1)^n x^{\overline{n}} \\ x^n = \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix}x^{\underline{i}} (x)n=(1)nxnxn=i=0n{ni}xi
推入如下的公式:
x n = ∑ i = 0 n { n i } ( − 1 ) n − i x i ‾ x^n = \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix} (-1)^{n - i}x^{\overline{i}} xn=i=0n{ni}(1)nixi
请读者自行推导。

总结一下,我们已经得到了所有的六个上升幂、下降幂和普通幂之间的转换公式:
( − x ) n ‾ = ( − 1 ) n x n ‾ ( − x ) n ‾ = ( − 1 ) n x n ‾ x n ‾ = ∑ i = 0 n [ n i ] x i x n = ∑ i = 0 n { n i } x i ‾ x n ‾ = ∑ i = 0 n [ n i ] ( − 1 ) n − i x i x n = ∑ i = 0 n { n i } ( − 1 ) n − i x i ‾ (-x)^{\overline{n}} = (-1)^n x^{\underline{n}} \\ (-x)^{\underline{n}} = (-1)^n x^{\overline{n}} \\ x^{\overline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} x^i \\ x^n = \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix}x^{\underline{i}} \\ x^{\underline{n}} = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} (-1)^{n - i}x^i \\ x^n = \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix} (-1)^{n - i}x^{\overline{i}} (x)n=(1)nxn(x)n=(1)nxnxn=i=0n[ni]xixn=i=0n{ni}xixn=i=0n[ni](1)nixixn=i=0n{ni}(1)nixi
下文有可能会分别称它们为公式一~公式六。

如果我们将公式三代入公式六,可以得到:
x n = ∑ i = 0 n { n i } ( − 1 ) n − i x i ‾ = ∑ i = 0 n { n i } ( − 1 ) n − i ∑ j = 0 i [ i j ] x j = ∑ j = 0 n x j ∑ i = j n { n i } [ i j ] ( − 1 ) n − i \begin{aligned} x^n &= \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix} (-1)^{n - i}x^{\overline{i}} \\ &= \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix} (-1)^{n - i}\sum_{j = 0}^i \begin{bmatrix}i \\ j\end{bmatrix} x^j \\ &= \sum_{j = 0}^nx^j\sum_{i = j}^n\begin{Bmatrix}n \\ i\end{Bmatrix} \begin{bmatrix}i \\ j\end{bmatrix} (-1)^{n - i} \end{aligned} xn=i=0n{ni}(1)nixi=i=0n{ni}(1)nij=0i[ij]xj=j=0nxji=jn{ni}[ij](1)ni
可以发现有:
∑ i = j n { n i } [ i j ] ( − 1 ) n − i = [ j = n ] \sum_{i = j}^n\begin{Bmatrix}n \\ i\end{Bmatrix} \begin{bmatrix}i \\ j\end{bmatrix} (-1)^{n - i} = [j = n] i=jn{ni}[ij](1)ni=[j=n]
类似地,将公式四代入公式五也可以得到十分类似的结果,这里就把结论摆出来了:
∑ i = j n [ n i ] { i j } ( − 1 ) n − i = [ j = n ] \sum_{i = j}^n\begin{bmatrix}n \\ i\end{bmatrix} \begin{Bmatrix}i \\ j\end{Bmatrix} (-1)^{n - i} = [j = n] i=jn[ni]{ij}(1)ni=[j=n]
这俩公式合称反转公式

注意一点: − 1 -1 1 的指数 n − i n - i ni 也可以写成 j + i j + i j+i。当 j = n j = n j=n 时,这样不会有任何影响;当 j ≠ n j \ne n j=n 时,整个式子的值为 0 0 0,也不会受到影响。

j = 1 j = 1 j=1 时有:
∑ i = 1 n ( − 1 ) n − 1 ( i − 1 ) ! { n i } = [ n = 1 ] \sum_{i = 1}^n(-1)^{n - 1}(i - 1)!\begin{Bmatrix}n \\ i\end{Bmatrix} = [n = 1] i=1n(1)n1(i1)!{ni}=[n=1]
这个公式在斯特林反演中会经常用到。

由反转公式,我们还可以得到斯特林反演的另一种形式:
f n = ∑ i = 0 n { n i } g i ⇓ g n = ∑ i = 0 n ( − 1 ) n − i [ n i ] f i f_n = \sum_{i = 0}^n \begin{Bmatrix}n \\ i\end{Bmatrix} g_i \\ \Downarrow \\ g_n = \sum_{i = 0}^n(-1)^{n -i} \begin{bmatrix}n \\ i\end{bmatrix} f_i fn=i=0n{ni}gign=i=0n(1)ni[ni]fi
这个形式十分类似二项式反演,显得有许些亲切感。

例题

  • BZOJ 4671

题解:

直接判连通并不好入手。相反,强制不联通却简单得多。

考虑容斥,设 f i f_i fi 为把 n n n 个点划分为 i i i互相独立且互不相交的点集的方案数, g i g_i gi 为图中恰好有 i i i连通块的方案数。

那么对于 g j g_j gj,它对 f i f_i fi 的贡献为: { j i } \begin{Bmatrix}j \\ i\end{Bmatrix} {ji}

因为 g j g_j gj 中的 j j j 个连通块互不相同,同时 f i f_i fi 中的 i i i 个点集又不考虑顺序,这恰好满足了第二类斯特林数的要求。

即:
f i = ∑ j = i n { j i } g j f_i = \sum_{j = i}^n \begin{Bmatrix}j \\ i\end{Bmatrix} g_j fi=j=in{ji}gj
反演一下就有:
g i = ∑ j = i n ( − 1 ) j − i [ j i ] f j g_i = \sum_{j = i}^n(-1)^{j - i}\begin{bmatrix}j \\ i\end{bmatrix} f_j gi=j=in(1)ji[ji]fj
依照题意,现在我们只需求出 g 1 = ∑ j = 1 n ( − 1 ) j − 1 ( j − 1 ) ! f j g_1 = \sum_{j = 1}^n (-1)^{j - 1}(j - 1)! f_j g1=j=1n(1)j1(j1)!fj,那么最大的问题是求 f j f_j fj

首先我们可以枚举子集划分(贝尔数级别,不会超时),然后我们强制让两个点集之间不连任何一条边。

由于一条边的选择情况取决于包含这条边的图的选择情况,因此对于一条横跨两个点集的边 ( u , v ) (u, v) (u,v),我们设包含它的图为 G 1 ∼ m ′ G'_{1\sim m} G1m,同时设每个图的选择情况为 x 1 ∼ m x_{1\sim m} x1m 0 0 0 表示不选, 1 1 1 表示选),那么我们可以列出方程:
xor ⁡ i = 1 m x i = 0 \operatorname{xor}_{i = 1}^m x_i = 0 xori=1mxi=0
枚举完每条边之后,我们可以得到一个异或方程组,那么解的数量就是这个子集划分对 f j f_j fj 的贡献。

求解方程组的办法当然是用高斯消元。不过,如何求异或方程组解的数量呢?

如果您阅读过我的文章《线性空间和OI中的线性基》,那么这个问题是就易如反掌了。

由于线性基的构建过程本质上是高斯消元,因此当我们把一个方程插入线性基后,这个方程会被自动进行高斯消元,并且由于增广矩阵是一个零向量,所以无论怎样消都是没有任何影响的。

在插入所有方程后,我们可以发现,线性基中可能存在一些空的位置。这些空的位置,是高斯消元过程中可以被抵消掉的元,即自由元。这些自由元可以等于值域内的任意值(这里是 0 0 0 1 1 1),并且所有自由元的取值同时也决定了剩下的主元的取值,也就是说,每一组自由元的取值都对应原方程的一组解

所以说,我们只要把方程转化为二进制数插入线性基,再统计有多少个位置等于 0 0 0 即可。设这个值为 c n t cnt cnt,那么我们要求的答案就是 2 c n t 2^{cnt} 2cnt

当然,这道题线性基的构建方法不能使用高斯消元法(会被卡常),用普通的构建方法即可。

代码:

#include
#include
#include
#include
#include

#define MAXN 11
#define MAXM 61

using LL = long long;
using namespace std;

int s, n, mp[MAXN], in[MAXM];
LL fac[MAXN], ans;
bool t[MAXM][MAXN][MAXN];
char g[MAXN * MAXN];

namespace Linear_Basis
{
	int cnt;
	LL b[MAXM];

	inline void Insert(LL val)
	{
		for(register int i = s - 1; i >= 0; i--)
		{
			if((val >> i & 1LL) ^ 1) continue;
			if(b[i]) { val ^= b[i]; continue; }
			++cnt, b[i] = val;
			break;
		}
		return;
	}
}

using namespace Linear_Basis;

inline void DFS(int i, int j)
{
	if(i == n + 1)
	{
		cnt = 0, memset(b, 0LL, sizeof b);
		for(register int a = 1; a < n; a++)
		{
			for(register int c = a + 1; c <= n; c++)
			{
				if(in[a] == in[c]) continue;
				LL val = 0LL;
				for(register int q = 0; q < s; q++) if(t[q][a][c]) val |= 1LL << q;
				Insert(val);
			}
		}
		LL f = 1LL << (s - cnt);
		ans += (j & 1 ? 1LL : -1LL) * fac[j - 1] * f;
		return;
	}
	for(register int k = 1; k <= j; k++) in[i] = k, DFS(i + 1, j); 
	in[i] = j + 1, DFS(i + 1, j + 1);
	return;
}

int main()
{
	for(register int i = 2; i < MAXN; i++) mp[i * (i - 1) / 2] = i;
	scanf("%d", &s);
	for(register int i = 0; i < s; i++)
	{
		scanf("%s", g);
		n = mp[(int)strlen(g)];
		int tot = 0;
		for(register int j = 1; j < n; j++)
		{
			for(register int k = j + 1; k <= n; k++)
			{
				if(g[tot] == '1') t[i][j][k] = true;
				++tot;
			}
		}
	}
	fac[0] = 1LL; for(register int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i;
	DFS(1, 0);
	printf("%lld\n", ans);
	return 0;
}
  • CF961G Partitions

抛开 w i w_i wi 不谈,可以发现单个物品的贡献是一定的,所以我们只要把 w i w_i wi 求和,乘上单个物品的贡献 δ \delta δ 即可。

现在考虑计算 δ \delta δ
δ = ∑ i = 1 n i ( n − 1 i − 1 ) { n − i k − 1 } = ∑ i = 1 n i ( n − 1 i − 1 ) ∑ j = 0 k − 1 ( − 1 ) k − j − 1 ( k − j − 1 ) ! j n − i j ! = ∑ j = 0 k − 1 ( − 1 ) k − j − 1 ( k − j − 1 ) ! j ! ∑ i = 1 n i j n − i ( n − 1 i − 1 ) \begin{aligned} \delta =&\sum_{i = 1}^n i \displaystyle\binom{n - 1}{i - 1} \begin{Bmatrix}n - i \\ k - 1\end{Bmatrix} \\ =&\sum_{i = 1}^n i \displaystyle\binom{n - 1}{i - 1} \sum_{j = 0}^{k - 1}\frac{(-1)^{k - j - 1}}{(k - j - 1)!}\frac{j^{n - i}}{j!} \\ =&\sum_{j = 0}^{k - 1} \frac{(-1)^{k - j - 1}}{(k - j - 1)! j!}\sum_{i = 1}^ni j^{n - i}\displaystyle\binom{n - 1}{i - 1} \end{aligned} δ===i=1ni(i1n1){nik1}i=1ni(i1n1)j=0k1(kj1)!(1)kj1j!jnij=0k1(kj1)!j!(1)kj1i=1nijni(i1n1)
前面的式子没有什么问题,考虑如何求后面的式子:
∑ i = 1 n i j n − i ( n − 1 i − 1 ) = ∑ i = 1 n ( i − 1 + 1 ) j n − i ( n − 1 i − 1 ) = ∑ i = 1 n ( i − 1 ) j n − i ( n − 1 i − 1 ) + ∑ i = 1 n j n − i ( n − 1 i − 1 ) = ∑ i = 1 n ( i − 1 ) j n − i ( n − 1 i − 1 ) + ( j + 1 ) n − 1 = ( n − 1 ) ∑ i = 1 n j n − i ( n − 2 i − 2 ) + ( j + 1 ) n − 1 = ( n − 1 ) ( j + 1 ) n − 2 + ( j + 1 ) n − 1 = ( j + 1 ) n − 2 ( n + j ) \begin{aligned} &\sum_{i = 1}^ni j^{n - i}\displaystyle\binom{n - 1}{i - 1} \\ =&\sum_{i = 1}^n(i - 1 + 1) j^{n - i}\displaystyle\binom{n - 1}{i - 1} \\ =&\sum_{i = 1}^n(i - 1) j^{n - i}\displaystyle\binom{n - 1}{i - 1} + \sum_{i = 1}^nj^{n - i}\displaystyle\binom{n - 1}{i - 1} \\ =&\sum_{i = 1}^n(i - 1) j^{n - i}\displaystyle\binom{n - 1}{i - 1} + (j + 1)^{n - 1} \\ =&(n - 1)\sum_{i = 1}^n j^{n - i}\displaystyle\binom{n - 2}{i - 2} + (j + 1)^{n - 1} \\ =&(n - 1)(j + 1)^{n - 2} + (j + 1)^{n - 1} \\ =&(j + 1)^{n - 2}(n + j) \end{aligned} ======i=1nijni(i1n1)i=1n(i1+1)jni(i1n1)i=1n(i1)jni(i1n1)+i=1njni(i1n1)i=1n(i1)jni(i1n1)+(j+1)n1(n1)i=1njni(i2n2)+(j+1)n1(n1)(j+1)n2+(j+1)n1(j+1)n2(n+j)
这样就做完了。

不过,这可是 CF ⁡ \operatorname{CF} CF 诶!有多少人能够在如此紧张的环境下推出这么一长串式子呢?

有没有更简单的做法?当然有!

组合意义天地灭,代数推导保平安。 —— tiger0133 ⁡ \operatorname{tiger0133} tiger0133

我们从组合意义思考一下,集合大小的本质是什么?不就是集合里的每一个数都贡献 1 1 1 吗!

所以说,还是考虑计算单个物品 i i i 的贡献:

首先,对于所有的 { n k } \begin{Bmatrix}n \\ k\end{Bmatrix} {nk} 个划分方案,自己都会对自己造成 1 1 1 的贡献。

其次,对于其它的每一个物品 j j j,抛开这个物品 j j j 不谈,我们把剩下的 n − 1 n - 1 n1 个物品分成 k k k 组(一共 { n − 1 k } \begin{Bmatrix}n - 1\\ k\end{Bmatrix} {n1k} 种方案),再把物品 j j j 加入到 i i i 所在的组内,这样 j j j 就对 i i i 造成了 1 1 1 的贡献。

所以单个物品贡献为:
{ n k } + ( n − 1 ) { n − 1 k } \begin{Bmatrix}n \\ k\end{Bmatrix} + (n - 1)\begin{Bmatrix}n - 1 \\ k\end{Bmatrix} {nk}+(n1){n1k}
代码:

#include
#include
#include

#define MAXN 200005
#define MOD 1000000007

using namespace std;

int n, k, sum, val1, val2, fac, inv[MAXN];

inline int Pow(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;
}

int main()
{
	scanf("%d%d", &n, &k);
	for(register int i = 1; i <= n; i++)
	{
		int w; scanf("%d", &w);
		sum = (sum + w) % MOD;
	}
	fac = 1;
	for(register int i = 1; i <= k; i++) fac = 1LL * fac * i % MOD;
	inv[k] = Pow(fac, MOD - 2);
	for(register int i = k - 1; i >= 0; i--) inv[i] = 1LL * inv[i + 1] * (i + 1) % MOD;
	for(register int i = 0, w = 1; i <= k; i++)
	{
		val1 = (val1 + 1LL * w * inv[i] % MOD * Pow(k - i, n) % MOD * inv[k - i] % MOD) % MOD;
		val2 = (val2 + 1LL * w * inv[i] % MOD * Pow(k - i, n - 1) % MOD * inv[k - i] % MOD) % MOD;
		w = 1LL * w * (MOD - 1) % MOD;
	}
	printf("%lld\n", 1LL * sum * (val1 + 1LL * val2 * (n - 1) % MOD) % MOD);
	return 0;
}

T o   B e   C o n t i n u e d ⋯ ⋯ \mathit{To~Be~Continued\cdots\cdots} To Be Continued

你可能感兴趣的:(学习笔记,数学)