第一类斯特林数
第一类斯特林数 $s(n, m)$ 表示长度为 $n$ 的置换有 $m$ 个环的方案数。
第一类斯特林数有递推式 $s(n, m) = s(n - 1, m - 1) + s(n - 1, m) \times (n - 1)$,(讨论第 $n$ 个数新开一个环或者接在之前某个数后面),还有 $s(n, m) = \sum_{i = 1}^n s(n - i, m - 1) \times (i - 1)! \times C(n - 1, i - 1)$(枚举第一个数所在的环的大小,并考虑其他数的排法)。
可以用分治 FFT 在 $O(n \log^2 n)$ 的时间内求出第一类斯特林数的一行。
Count The Buildings
题目描述
求前缀递增单调栈长度为 $x$,后缀递增单调栈长度为 $y$ 的长度为 $n$ 的排列个数。
数据范围:$T \le 10^5, n, x, y \le 2000$。
思路分析
考虑排列中 $n$ 的位置,它左边形成了 $x - 1$ 组 $a_{l_i}, a_{l_i + 1}, \cdots, a_{r_i}$,其中 $l_i \le r_i, r_i + 1 = l_{i - 1}$,满足 $a_{l_i} > a_{l_i + 1}, a_{l_i + 2}, \cdots, a_{r_i}$,并且 $a_{l_{i}} < a_{l_{i + 1}}$。其实就是左边的置换有 $x - 1$ 个环的方案数(对于每个环把最大值转到第一位,并把环按照最大值排序),右边同理。问题变成了左边 $x - 1 + y - 1$ 个置换,选 $x - 1$ 个放左边的方案数,也就是 $s(n - 1, x + y - 2) \times C(x + y - 2, x - 1)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 2e3, mod = 1e9 + 7; |
5 | int T, n, l, r, c[maxn + 3][maxn + 3], s[maxn + 3][maxn + 3]; |
6 | |
7 | int main() { |
8 | s[0][0] = 1; |
9 | for (int i = 0; i <= maxn; i++) { |
10 | c[i][0] = 1; |
11 | } |
12 | for (int i = 1; i <= maxn; i++) { |
13 | for (int j = 1; j <= i; j++) { |
14 | c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod; |
15 | s[i][j] = (s[i - 1][j - 1] + 1ll * s[i - 1][j] * (i - 1)) % mod; |
16 | } |
17 | } |
18 | scanf("%d", &T); |
19 | while (T --> 0) { |
20 | scanf("%d %d %d", &n, &l, &r); |
21 | if (l + r - 2 > n - 1) { |
22 | puts("0"); |
23 | } else { |
24 | printf("%lld\n", 1ll * s[n - 1][l + r - 2] * c[l + r - 2][l - 1] % mod); |
25 | } |
26 | } |
27 | return 0; |
28 | } |
第二类斯特林数
第二类斯特林数 $S(n, m)$ 表示 $n$ 个不同元素分成 $m$ 个集合的方案数。
第二类斯特林数有递推式 $S(n, m) = S(n - 1, m - 1) + S(n - 1,m) \times m$(枚举最后一个数新开一个集合或插入之前的一个集合),还有 $S(n, m) = \sum_{i = 1}^m S(n - i, m - 1) \times C(n - 1, i - 1)$(枚举第一个数所在集合的大小,然后考虑其他数的选法)。
有恒等式 $n^m = \sum_{i = 0}^{n} S(m, i) \times i! \times C(n, i) = \sum_{i = 0}^{n} S(m, i) \times n^{\underline{i}}$,这样可以把 $x^k$ 转化成下降幂的形式。
第二类斯特林数·行
题目描述
求 $S(n, 0), S(n, 1), \cdots, S(n, n)$。
数据范围:$n \le 2 \times 10^5$。
思路分析
前置知识——二项式反演:
那么,利用二项式反演,我们可以推出第二类斯特林数的通项公式:
当然这个式子也可以用容斥原理来解释。式子变形一下可得:
可以把 $i$ 看作空集合的个数,我们把元素随意放在其他的集合中,然后进行容斥。最后由于盒子是相同的,答案要除以 $m!$。
构造多项式 $A_i = \sum_{i} \frac{i^n}{i!} x^i, B_i = \frac{(-1)^{i}}{(i)!} x_i$ 进行卷积即可求出 $S(n, 0), S(n, 1), \cdots, S(n, n)$。时间复杂度 $O(n \log n)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 1 << 19, mod = 167772161, g = 3; |
5 | int n, p_n[maxn + 3], fact[maxn + 3], finv[maxn + 3]; |
6 | int lim, k, rev[maxn + 3], A[maxn + 3], B[maxn + 3], C[maxn + 3]; |
7 | |
8 | int f(int x) { |
9 | return x < mod ? x : x - mod; |
10 | } |
11 | |
12 | int qpow(int a, int b) { |
13 | if (b < 0) b += mod - 1; |
14 | int c = 1; |
15 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
16 | if (b & 1) c = 1ll * a * c % mod; |
17 | } |
18 | return c; |
19 | } |
20 | |
21 | void prework(int n) { |
22 | for (int i = 1; i <= n; i++) { |
23 | p_n[i] = qpow(i, n); |
24 | } |
25 | fact[0] = 1; |
26 | for (int i = 1; i <= n; i++) { |
27 | fact[i] = 1ll * fact[i - 1] * i % mod; |
28 | } |
29 | finv[n] = qpow(fact[n], mod - 2); |
30 | for (int i = n; i; i--) { |
31 | finv[i - 1] = 1ll * finv[i] * i % mod; |
32 | } |
33 | } |
34 | |
35 | void dft(int A[], int n, int t) { |
36 | for (int i = 0; i < n; i++) if (i < rev[i]) { |
37 | swap(A[i], A[rev[i]]); |
38 | } |
39 | for (int k = 1; k < n; k <<= 1) { |
40 | int x = qpow(g, (mod - 1) / (k << 1) * t); |
41 | for (int i = 0; i < n; i += k << 1) { |
42 | int y = 1; |
43 | for (int j = i; j < i + k; j++, y = 1ll * x * y % mod) { |
44 | int p = A[j], q = 1ll * A[j + k] * y % mod; |
45 | A[j] = f(p + q), A[j + k] = f(p - q + mod); |
46 | } |
47 | } |
48 | } |
49 | if (t == -1) { |
50 | int x = qpow(n, mod - 2); |
51 | for (int i = 0; i < n; i++) { |
52 | A[i] = 1ll * A[i] * x % mod; |
53 | } |
54 | } |
55 | } |
56 | |
57 | int main() { |
58 | scanf("%d", &n); |
59 | prework(n); |
60 | for (int i = 0; i <= n; i++) { |
61 | A[i] = 1ll * p_n[i] * finv[i] % mod; |
62 | } |
63 | for (int i = 0; i <= n; i++) { |
64 | B[i] = 1ll * (i & 1 ? mod - 1 : 1) * finv[i] % mod; |
65 | } |
66 | for (lim = 1, k = 0; lim <= n * 2; lim <<= 1) k++; |
67 | for (int i = 1; i < lim; i++) { |
68 | rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1)); |
69 | } |
70 | dft(A, lim, 1), dft(B, lim, 1); |
71 | for (int i = 0; i < lim; i++) { |
72 | C[i] = 1ll * A[i] * B[i] % mod; |
73 | } |
74 | dft(C, lim, -1); |
75 | for (int i = 0; i <= n; i++) { |
76 | printf("%d%c", C[i], " \n"[i == n]); |
77 | } |
78 | return 0; |
79 | } |
Crash 的文明世界
题目描述
给定 $n$ 个点的树,边权为 $1$,对于每个 $i$,求出 $\sum_{j = 1}^{n} \text{dist}(i, j)^k$。
数据范围:$n \le 5 \times 10^4, k \le 150$。
思路分析
首先我们有 $x^k = \sum_{i = 0}^{k} S(k, i) \times i! \times C(x, i)$,我们将 $\text{dist}(i, j)$ 代入其中,这样我们就只用求 $\sum_{j = 1}^{n} C(\text{dist}(i, j), k)$ 即可,而这个显然可以树形 dp 做。时间复杂度 $O(nk)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 5e4, maxm = 150, mod = 1e4 + 7; |
5 | int n, m, S[maxm + 3][maxm + 3], dp[maxn + 3][maxm + 3]; |
6 | int F[maxm + 3], f[maxn + 3][maxm + 3], up[maxn + 3][maxm + 3]; |
7 | vector<int> G[maxn + 3]; |
8 | |
9 | int func(int x) { |
10 | return x < mod ? x : x - mod; |
11 | } |
12 | |
13 | void dfs(int u, int pa = 0) { |
14 | dp[u][0] = 1; |
15 | for (int i = 0, v; i < G[u].size(); i++) { |
16 | v = G[u][i]; |
17 | if (v == pa) continue; |
18 | dfs(v, u); |
19 | dp[u][0] = func(dp[u][0] + dp[v][0]); |
20 | for (int j = 1; j <= m; j++) { |
21 | dp[u][j] = func(dp[u][j] + dp[v][j]); |
22 | dp[u][j] = func(dp[u][j] + dp[v][j - 1]); |
23 | } |
24 | } |
25 | } |
26 | |
27 | void solve(int u, int pa = 0) { |
28 | for (int i = 0, v; i < G[u].size(); i++) { |
29 | v = G[u][i]; |
30 | if (v == pa) continue; |
31 | for (int j = 0; j <= m; j++) { |
32 | f[u][j] = func(up[u][j] + dp[u][j]); |
33 | } |
34 | f[u][0] = func(f[u][0] - dp[v][0] + mod); |
35 | for (int j = 1; j <= m; j++) { |
36 | f[u][j] = func(f[u][j] - dp[v][j] + mod); |
37 | f[u][j] = func(f[u][j] - dp[v][j - 1] + mod); |
38 | } |
39 | up[v][0] = f[u][0]; |
40 | for (int j = 1; j <= m; j++) { |
41 | up[v][j] = func(up[v][j] + f[u][j]); |
42 | up[v][j] = func(up[v][j] + f[u][j - 1]); |
43 | } |
44 | solve(v, u); |
45 | } |
46 | } |
47 | |
48 | int main() { |
49 | scanf("%d %d", &n, &m); |
50 | S[0][0] = 1; |
51 | for (int i = 1; i <= m; i++) { |
52 | for (int j = 1; j <= m; j++) { |
53 | S[i][j] = (S[i - 1][j - 1] + S[i - 1][j] * j) % mod; |
54 | } |
55 | } |
56 | F[0] = 1; |
57 | for (int i = 1; i <= m; i++) { |
58 | F[i] = 1ll * F[i - 1] * i % mod; |
59 | } |
60 | for (int i = 1, u, v; i < n; i++) { |
61 | scanf("%d %d", &u, &v); |
62 | G[u].push_back(v), G[v].push_back(u); |
63 | } |
64 | dfs(1); |
65 | solve(1); |
66 | for (int i = 1; i <= n; i++) { |
67 | int ans = 0; |
68 | for (int j = 0; j <= m; j++) { |
69 | int x = func(dp[i][j] + up[i][j]); |
70 | ans = (ans + 1ll * S[m][j] * F[j] * x) % mod; |
71 | } |
72 | printf("%d\n", ans); |
73 | } |
74 | return 0; |
75 | } |
求和
题目描述
对于给定的 $n$,求:$f(n) = \sum_{i = 0}^{n} \sum_{j = 0}^{i} S(i, j) \times 2^j \times j!$,对 $998244353$ 取模。
数据范围:$n \le 10^5$。
思路分析
考虑 $g(n) = \sum_{i = 0}^{n} S(n, i) \times 2^i \times i!$ 的组合意义:将 $n$ 个元素放进若干个有序的集合,每个有元素的集合有两种状态。那么我们就有递推式:$g(n) = \sum_{i = 1}^{n} C(n, i) \times g(n - i) \times 2$,思路是枚举某个集合的大小。使用分治 FFT 计算即可,时间复杂度 $O(n \log^2 n)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 1 << 18, mod = 998244353; |
5 | int n, f[maxn + 3], g[maxn + 3], h[maxn + 3], fact[maxn + 3], finv[maxn + 3]; |
6 | int lim, k, rev[maxn + 3], A[maxn + 3], B[maxn + 3], C[maxn + 3]; |
7 | |
8 | int func(int x) { |
9 | return x < mod ? x : x - mod; |
10 | } |
11 | |
12 | int qpow(int a, int b) { |
13 | if (b < 0) b += mod - 1; |
14 | int c = 1; |
15 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
16 | if (b & 1) c = 1ll * a * c % mod; |
17 | } |
18 | return c; |
19 | } |
20 | |
21 | void prework(int n) { |
22 | fact[0] = 1; |
23 | for (int i = 1; i <= n; i++) { |
24 | fact[i] = 1ll * fact[i - 1] * i % mod; |
25 | } |
26 | finv[n] = qpow(fact[n], mod - 2); |
27 | for (int i = n; i; i--) { |
28 | finv[i - 1] = 1ll * finv[i] * i % mod; |
29 | } |
30 | } |
31 | |
32 | void dft(int A[], int n, int t) { |
33 | for (int i = 0; i < n; i++) if (i < rev[i]) { |
34 | swap(A[i], A[rev[i]]); |
35 | } |
36 | for (int k = 1; k < n; k <<= 1) { |
37 | int x = qpow(3, (mod - 1) / (k << 1) * t); |
38 | for (int i = 0; i < n; i += k << 1) { |
39 | int y = 1; |
40 | for (int j = i; j < i + k; j++, y = 1ll * x * y % mod) { |
41 | int p = A[j], q = 1ll * A[j + k] * y % mod; |
42 | A[j] = func(p + q), A[j + k] = func(p - q + mod); |
43 | } |
44 | } |
45 | } |
46 | if (t == -1) { |
47 | int x = qpow(n, mod - 2); |
48 | for (int i = 0; i < n; i++) { |
49 | A[i] = 1ll * A[i] * x % mod; |
50 | } |
51 | } |
52 | } |
53 | |
54 | void cdq(int l, int r) { |
55 | if (l == 0 && r == 0) { |
56 | return; |
57 | } |
58 | if (l == r) { |
59 | g[l] = 2ll * g[l] * fact[l] % mod; |
60 | h[l] = 1ll * g[l] * finv[l] % mod; |
61 | return; |
62 | } |
63 | int mid = (l + r) >> 1; |
64 | cdq(l, mid); |
65 | int p = mid - l, q = r - l; |
66 | for (int i = 0; i <= p; i++) { |
67 | A[i] = h[i + l]; |
68 | } |
69 | for (int i = 1; i <= q; i++) { |
70 | B[i] = f[i]; |
71 | } |
72 | for (lim = 1, k = 0; lim <= p + q; lim <<= 1) k++; |
73 | for (int i = 1; i < lim; i++) { |
74 | rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1)); |
75 | } |
76 | fill(A + p + 1, A + lim, 0); |
77 | fill(B + q + 1, B + lim, 0); |
78 | dft(A, lim, 1), dft(B, lim, 1); |
79 | for (int i = 0; i < lim; i++) { |
80 | C[i] = 1ll * A[i] * B[i] % mod; |
81 | } |
82 | dft(C, lim, -1); |
83 | for (int i = 1; i <= q - p; i++) { |
84 | g[i + mid] = (g[i + mid] + C[i + p]) % mod; |
85 | } |
86 | cdq(mid + 1, r); |
87 | } |
88 | |
89 | int main() { |
90 | scanf("%d", &n); |
91 | prework(n); |
92 | for (int i = 1; i <= n; i++) { |
93 | f[i] = finv[i]; |
94 | } |
95 | g[0] = 1, h[0] = 1; |
96 | cdq(0, n); |
97 | int ans = 0; |
98 | for (int i = 0; i <= n; i++) { |
99 | ans = func(ans + g[i]); |
100 | } |
101 | printf("%d\n", ans); |
102 | return 0; |
103 | } |
Partitions
题目描述
给定 $n$ 个元素 $w_1, w_2, \cdots, w_n$,定义一个集合 $S$ 的权值为 $\vert S \vert \times \sum_{i \in S} w_i$,定义一个划分的权值为分出所有集合的权值和,求将这 $n$ 个元素划成 $k$ 个集合的权值和之和。
数据范围:$n, k \le 2 \times 10^5$。
思路分析
记 $W = \sum_{i = 1}^{n} w_i$,枚举某个点所在集合的大小可得答案等于:
那么我们开始推式子:
那么我们直接计算即可。
当然也有巧妙一点的方法:考虑某个元素对答案的贡献。首先每个元素都会贡献自己,贡献是 $w_i \times S(n, k)$;另外如果它和其他元素同在一个集合,我们让该集合中的每个其他元素都给它 $1$ 的贡献,也就是说我们可以先从剩下 $n - 1$ 个元素中选 $k$ 个集合,然后 $n - 1$ 个元素都给新加入的元素贡献 $1$,总贡献就是 $w_i \times (n - 1) \times S(n - 1, k)$。所以答案就是 $W \times \left[ S(n, k) + (n - 1) \times S(n - 1, k) \right]$,直接计算即可。时间复杂度 $O(n)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 2e5, mod = 1e9 + 7; |
5 | int n, k, sum, num; |
6 | |
7 | int func(int x) { |
8 | return x < mod ? x : x - mod; |
9 | } |
10 | |
11 | int qpow(int a, int b) { |
12 | int c = 1; |
13 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
14 | if (b & 1) c = 1ll * a * c % mod; |
15 | } |
16 | return c; |
17 | } |
18 | |
19 | int S(int n, int m) { |
20 | int ret = 0, cur = 1; |
21 | for (int i = 1; i <= m; i++) { |
22 | cur = 1ll * cur * qpow(i, mod - 2) % mod; |
23 | } |
24 | for (int i = 0; i <= m; i++) { |
25 | ret = (ret + 1ll * (i & 1 ? mod - 1 : 1) * cur % mod * qpow(m - i, n)) % mod; |
26 | cur = 1ll * cur * (m - i) % mod * qpow(i + 1, mod - 2) % mod; |
27 | } |
28 | return ret; |
29 | } |
30 | |
31 | int main() { |
32 | scanf("%d %d", &n, &k); |
33 | for (int i = 1, x; i <= n; i++) { |
34 | scanf("%d", &x); |
35 | sum = func(sum + x); |
36 | } |
37 | num = (S(n, k) + 1ll * S(n - 1, k) * (n - 1)) % mod; |
38 | printf("%lld\n", 1ll * sum * num % mod); |
39 | return 0; |
40 | } |
异或图
题目描述
给定 $n$ 张 $m$ 个点的无向图,两个图的异或定义为邻接矩阵的异或,求所有 $2^n$ 个子集的图的异或和中有多少图是联通的。
数据范围:$n \le 60, m \le 10$。
思路分析
前置技能——斯特林反演(的变形?):
$F(m) = \sum_{i = m}^{n} S(i, m) \times G(i)$
$G(m) = \sum_{i = m}^{n} (-1)^{i - m} \times s(i, m) \times F(i)$
我们考虑枚举全集的一个划分,强制划分出的任意两个集合之间不能有边(集合内部可以有边)。那么对于一个有 $m$ 个联通块的异或图,如果我们强制划出了 $n$ 个集合,这个方案会被计算 $S(m, n)$ 次。相当于如果我们强制划出 $i$ 个集合算出的方案数为 $F_i$,有 $i$ 个联通块的异或图共有 $G_i$ 个,那么 $F, G$ 就满足上面斯特林反演的关系。所以,我们先枚举划分,然后用线性基求出满足划分条件的异或图个数,从而求出 $F$ 数组,最后用斯特林反演求出 $G(1)$ 即可(注:$s(n, 1) = (n - 1)!$)。时间复杂度 $O(B(m) \cdot n \cdot \frac{m (m - 1)}{2})$,其中 $B(n) = \sum_{i = 0}^{n} S(n, i)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | typedef long long ll; |
5 | const int maxn = 60, maxm = 10; |
6 | int n, m, a[maxn + 3][maxm + 3][maxm + 3]; |
7 | int id[maxm + 3], tmp[maxm + 3][maxm + 3]; |
8 | ll f[maxm + 3], num[maxn + 3], base[maxn + 3]; |
9 | char s[maxn + 3]; |
10 | |
11 | bool insert(ll x, int n) { |
12 | for (int i = n; ~i; i--) { |
13 | if (x >> i & 1) { |
14 | if (!base[i]) { |
15 | base[i] = x; |
16 | return true; |
17 | } |
18 | x ^= base[i]; |
19 | } |
20 | } |
21 | return false; |
22 | } |
23 | |
24 | ll solve() { |
25 | for (int i = 1; i <= n; i++) { |
26 | num[i] = 0; |
27 | } |
28 | int p = 0; |
29 | for (int i = 1; i <= m; i++) { |
30 | for (int j = i + 1; j <= m; j++) { |
31 | if (id[i] != id[j]) { |
32 | for (int k = 1; k <= n; k++) { |
33 | if (a[k][i][j]) { |
34 | num[k] |= 1ll << (ll) p; |
35 | } |
36 | } |
37 | p++; |
38 | } |
39 | } |
40 | } |
41 | for (int i = 0; i < p; i++) { |
42 | base[i] = 0; |
43 | } |
44 | ll ret = 1; |
45 | for (int i = 1; i <= n; i++) { |
46 | if (!insert(num[i], p - 1)) ret <<= 1; |
47 | } |
48 | return ret; |
49 | } |
50 | |
51 | void dfs(int lft, int dep) { |
52 | if (lft == 0) { |
53 | f[dep] += solve(); |
54 | return; |
55 | } |
56 | int x = 0; |
57 | for (int i = 1; i <= m; i++) { |
58 | if (!id[i]) { |
59 | x = i; |
60 | break; |
61 | } |
62 | } |
63 | int d = dep + 1, cnt = 0, *cur = tmp[d]; |
64 | for (int i = x + 1; i <= m; i++) { |
65 | if (!id[i]) cur[++cnt] = i; |
66 | } |
67 | id[x] = d; |
68 | for (int msk = 0; msk < 1 << cnt; msk++) { |
69 | int y = lft - 1; |
70 | for (int i = 1; i <= cnt; i++) { |
71 | if (msk >> (i - 1) & 1) { |
72 | id[cur[i]] = d, y--; |
73 | } |
74 | } |
75 | dfs(y, d); |
76 | for (int i = 1; i <= cnt; i++) { |
77 | if (msk >> (i - 1) & 1) { |
78 | id[cur[i]] = 0; |
79 | } |
80 | } |
81 | } |
82 | } |
83 | |
84 | int main() { |
85 | scanf("%d", &n); |
86 | for (int i = 1; i <= n; i++) { |
87 | scanf("%s", s + 1); |
88 | if (i == 1) { |
89 | int t = strlen(s + 1); |
90 | for (int j = 2; j <= maxm; j++) { |
91 | if (j * (j - 1) == t * 2) { |
92 | m = j; |
93 | break; |
94 | } |
95 | } |
96 | } |
97 | int p = 0; |
98 | for (int j = 1; j <= m; j++) { |
99 | for (int k = j + 1; k <= m; k++) { |
100 | a[i][j][k] = a[i][k][j] = s[++p] - '0'; |
101 | } |
102 | } |
103 | } |
104 | dfs(m, 0); |
105 | ll ans = 0, cur = 1; |
106 | for (int i = 1; i <= m; i++) { |
107 | ans += 1ll * (i & 1 ? 1 : -1) * cur * f[i]; |
108 | cur *= i; |
109 | } |
110 | printf("%lld\n", ans); |
111 | return 0; |
112 | } |