只要你跑的够快,锅就追不上你

0%

「学习笔记」斯特林数

第一类斯特林数

第一类斯特林数 $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

「HDU 4372」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
#include <bits/stdc++.h>
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$ 转化成下降幂的形式。

第二类斯特林数·行

「Luogu 5395」第二类斯特林数·行

题目描述

求 $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
#include <bits/stdc++.h>
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 的文明世界

「Luogu 4827」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
#include <bits/stdc++.h>
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
}

求和

「BZOJ 4555」求和

题目描述

对于给定的 $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
#include <bits/stdc++.h>
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

「Codeforces 961G」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
#include <bits/stdc++.h>
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
#include <bits/stdc++.h>
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
}