下面提到的内容均属组合数学范畴。
由于式子较多,为增强直观性,笔者将一改以往的笔风,用 ( 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 ‾ = ( − 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=0∑∞aixi
(严谨地讲, 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]=[n−1k−1]+(n−1)[n−1k]
考虑从组合意义去证明:加入一个数,要么自己单独构成一个圆排列,要么插入已有的圆排列。前面说过,后者的方案数为 n − 1 n - 1 n−1。所以,根据加法原理,即可得到该递推式。
当然,该递推式也存在边界条件: [ n 0 ] = [ n = 0 ] \begin{bmatrix}n \\ 0\end{bmatrix} = [n = 0] [n0]=[n=0]。
由于第一类斯特林数不存在通项公式,这个递推式就显得尤为重要了,它将辅助证明下面的诸多结论。
n ! = ∑ i = 0 n [ n i ] n! = \sum_{i = 0}^n \begin{bmatrix}n \\ i\end{bmatrix} n!=i=0∑n[ni]
前者实际上是指 1 ∼ n 1\sim n 1∼n 的全排列。由于每一种排列中,圆排列的个数是一定的,那么我们可以考虑枚举所有的圆排列个数,把对应的第一类斯特林数相加即为答案。
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=0∑n[ni]xi
证明:
考虑构造生成函数 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)=xfn−1(x)+(n−1)fn−1(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+n−1)fn−1(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。
受上面过程的启发,我们还可以考虑用数学归纳法证明。
显然当 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=0∑n+1[n+1i]xii=0∑n+1([ni−1]+n⋅[ni])xii=0∑n+1[ni−1]xi+i=0∑n+1n⋅[ni]xii=0∑n[ni]xi+1+n⋅i=0∑n[ni]xix⋅i=0∑n[ni]xi+n⋅i=0∑n[ni]xi(x+n)i=0∑n[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=0∑nai(x+n)ii=0∑naij=0∑i(ji)xjni−ji=0∑naij=0∑ij!(i−j)!i!xjni−jj=0∑nj!xji=j∑naii!(i−j)!ni−jj=0∑nj!xji=0∑n−jai+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=0jfigj−i。
如果我们把 f i f_i fi 这个数列翻转过来,还是做正常的卷积,得到的结果应该是: ∑ i = 0 j f n − i g j − i \sum_{i = 0}^jf_{n - i} g_{j - i} ∑i=0jfn−igj−i。
或者把 g i g_i gi 这个数列翻转过来,也做正常的卷积,得到的结果是: ∑ i = 0 j f i g n − j + i \sum_{i = 0}^jf_i g_{n - j + i} ∑i=0jfign−j+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=0n−jfi+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′=fn−i,则对 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=0∑jfn−j+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=0∑n−jfi+jgi=i=0∑n−jfn−(n−j)+igi=cn−j′
所以只要再把求出的 c ′ c' c′ 颠倒即为答案。
如果将 g i g_i gi 颠倒,即 g i ′ = g n − i g'_i = g_{n - i} gi′=gn−i,则对 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=0∑jfign−j+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=0∑n−jfi+jgi=i=j∑nfigi−j=i=0∑n+jfign−(n+j)+i=cj+n′
(注意:由于 f f f 和 g g g 都只有第 0 ∼ n 0 \sim n 0∼n 位不为 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;
}
题解:
做组合数学的题,光有推式子能力还不行,还得通过组合意义列出答案的式子。
例如这道题目,我们要思考的是,如何在其中加入组合意义。
可以发现,无论是从左边还是从右边,都可以并且一定会看到最高的楼。
然后,我们如果设 ∗ * ∗ 为能够看到的楼, . . . 为不能够看到的楼, H H H 为最高的楼,那么建筑一定是这种形式:
∗ . . . . ∗ . . ∗ . . ∗ . . . H . ∗ . . . . . ∗ . . ∗ *....*..*..*...H.*.....*..* ∗....∗..∗..∗...H.∗.....∗..∗
我们来从左侧的角度解释一下这个图的一些细节(右侧同理):
“顺序随意” 给了我们一定的启发,如果耐心把式子列出来,可以发现:如果我们把一个 ∗ * ∗ 和其右侧的 m m m 个 . . . 看作一个整体,那么由于一共有 ( m − 1 ) ! (m - 1)! (m−1)! 种排列方式。
如果你对排列数十分敏感,可以一眼看出——这就是圆排列。
圆排列!我们能想到什么?第一类斯特林数!
所以,我们只要在 n − 1 n - 1 n−1 个数中选出 A + B − 2 A + B - 2 A+B−2 个圆排列,并任选 A − 1 A-1 A−1 个放在 H H H 的左边, B − 1 B - 1 B−1 个放在 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} [n−1A+B−2]⋅(A−1A+B−2)。
直接预处理即可,时间在可承受范围内。
代码就不贴了。
把 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}={n−1k−1}+k⋅{n−1k}
还是考虑从组合意义去证明:加入一个数,要么自己单独构成一个集合,要么加入已有的 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=0∑k(−1)i(ik)(k−i)n=i=0∑ki!(−1)i⋅(k−i)!(k−i)n=i=0∑k(k−i)!(−1)k−i⋅i!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=0∑k(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=0∑k(−1)k−i(ik)fi=i=0∑k(−1)i(ik)(k−i)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=0∑k(−1)i(ik)(k−i)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=0∑x(ix){ni}i!=i=0∑n{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=0∑ki!(−1)i⋅(k−i)!(k−i)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;
}
“简单无向图” 是指无重边、无自环的无向图(不一定连通)。
一个带标号的图的价值定义为每个点度数的 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 1≤n≤109,1≤k≤2×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=n⋅2(2n−1)i=0∑n−1ik(in−1)
这个时候,如果只用一般的方法,大概率会无从下手。
回顾普通幂转化为第二类斯特林数的公式,我们可以得到:
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=n⋅2(2n−1)i=0∑n−1ik(in−1)=n⋅2(2n−1)i=0∑n−1(in−1)j=0∑min(i,k)(ji){kj}j!=n⋅2(2n−1)j=0∑min(n−1,k){kj}j!i=j∑n−1(in−1)(ji)=n⋅2(2n−1)j=0∑min(n−1,k){kj}j!(jn−1)i=j∑n−1(i−jn−j−1)=n⋅2(2n−1)j=0∑min(n−1,k){kj}j!(jn−1)i=0∑n−j−1(in−j−1)=n⋅2(2n−1)j=0∑min(n−1,k){kj}j!(jn−1)2n−j−1
由于本题没有提交地址,因此笔者偷懒没有写代码,望见谅~
在前面的学习中,我们知道有如下两个等式:
( − 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=0∑n[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=0∑n[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=0∑n[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=0∑n[ni](−1)n−ixi
类似的,我们可以由如下两个公式:
( − 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=0∑n{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=0∑n{ni}(−1)n−ixi
请读者自行推导。
总结一下,我们已经得到了所有的六个上升幂、下降幂和普通幂之间的转换公式:
( − 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=0∑n[ni]xixn=i=0∑n{ni}xixn=i=0∑n[ni](−1)n−ixixn=i=0∑n{ni}(−1)n−ixi
下文有可能会分别称它们为公式一~公式六。
如果我们将公式三代入公式六,可以得到:
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=0∑n{ni}(−1)n−ixi=i=0∑n{ni}(−1)n−ij=0∑i[ij]xj=j=0∑nxji=j∑n{ni}[ij](−1)n−i
可以发现有:
∑ 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=j∑n{ni}[ij](−1)n−i=[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=j∑n[ni]{ij}(−1)n−i=[j=n]
这俩公式合称反转公式。
注意一点: − 1 -1 −1 的指数 n − i n - i n−i 也可以写成 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=1∑n(−1)n−1(i−1)!{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=0∑n{ni}gi⇓gn=i=0∑n(−1)n−i[ni]fi
这个形式十分类似二项式反演,显得有许些亲切感。
题解:
直接判连通并不好入手。相反,强制不联通却简单得多。
考虑容斥,设 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=i∑n{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=i∑n(−1)j−i[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)j−1(j−1)!fj,那么最大的问题是求 f j f_j fj。
首先我们可以枚举子集划分(贝尔数级别,不会超时),然后我们强制让两个点集之间不连任何一条边。
由于一条边的选择情况取决于包含这条边的图的选择情况,因此对于一条横跨两个点集的边 ( u , v ) (u, v) (u,v),我们设包含它的图为 G 1 ∼ m ′ G'_{1\sim m} G1∼m′,同时设每个图的选择情况为 x 1 ∼ m x_{1\sim m} x1∼m( 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;
}
抛开 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=1∑ni(i−1n−1){n−ik−1}i=1∑ni(i−1n−1)j=0∑k−1(k−j−1)!(−1)k−j−1j!jn−ij=0∑k−1(k−j−1)!j!(−1)k−j−1i=1∑nijn−i(i−1n−1)
前面的式子没有什么问题,考虑如何求后面的式子:
∑ 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=1∑nijn−i(i−1n−1)i=1∑n(i−1+1)jn−i(i−1n−1)i=1∑n(i−1)jn−i(i−1n−1)+i=1∑njn−i(i−1n−1)i=1∑n(i−1)jn−i(i−1n−1)+(j+1)n−1(n−1)i=1∑njn−i(i−2n−2)+(j+1)n−1(n−1)(j+1)n−2+(j+1)n−1(j+1)n−2(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 n−1 个物品分成 k k k 组(一共 { n − 1 k } \begin{Bmatrix}n - 1\\ k\end{Bmatrix} {n−1k} 种方案),再把物品 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}+(n−1){n−1k}
代码:
#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;
}