斯特林数
斯特林数
第二类斯特林数
第二类斯特林数(斯特林子集数) 表示将 \(n\) 个两两不同的元素,划分为 \(k\) 个互不区分的非空子集的方案数,记作 \(n \brace k\) 。
递推式
组合意义:插入一个新元素时有两种方案:将新元素单独放入一个子集,或将新元素放入一个现有的非空子集。
通项公式
使用容斥原理证明该公式。设:
- \(G_m\) 表示将 \(n\) 个两两不同的元素,划分到 \(m\) 个两两不同的集合(允许空集)的方案数,则 \(G_m = m^n\) 。
- \(F_m\) 表示将 \(n\) 个两两不同的元素,划分到 \(m\) 个两两不同的非空集合(不允许空集)的方案数。
不难得到 \(G_m = \sum_{i = 0}^m \binom{m}{i} F_i\) ,二项式反演得到:
考虑 \(F_i\) 与 \(n \brace i\) 的关系,第二类斯特林数要求集合之间互不区分,因此 \(F_i\) 正好就是 \(n \brace i\) 的 \(i!\) 倍。
性质
组合意义:\(n\) 个带标号小球放进 \(m\) 个带标号盒子(允许空盒子)的方案数。
注意这里规定 \(0^0 = 1\) 。
第一类斯特林数
第一类斯特林数(斯特林轮换数)表示将 \(n\) 个两两不同的元素,划分为 \(k\) 个互不区分的非空轮换的方案数,记作 \({n \brack k}\) 。
递推式
组合意义:插入一个新元素时有两种方案:将该新元素置于一个单独的轮换中,或将该元素插入到任何一个现有的轮换中。
幂之间的转化
记上升幂 \(x^{\overline{n}} = \prod_{k = 0}^{n - 1} (x + k)\) ,下降幂 \(x^{\underline{n}} = \prod_{k = 0}^{n - 1} (x - k)\) ,则:
求行/列的斯特林数
P5395 第二类斯特林数·行
设 \(f(m)= m^n, g(m) = {n \brace m} m!\) ,则:
二项式反演得到:
直接卷积即可做到 \(O(n \log n)\) 。
inline void solve(int n, int *res) {
static int f[N], g[N];
for (int i = 0; i <= n; ++i)
f[i] = 1ll * sgn(i) * invfac[i] % Mod, g[i] = 1ll * mi(i, n) * invfac[i] % Mod;
int len = calc((n + 1) << 1);
clr(f + n + 1, len - n - 1), clr(g + n + 1, len - n - 1);
NTT(f, len, 1), NTT(g, len, 1);
for (int i = 0; i < len; ++i)
f[i] = 1ll * f[i] * g[i] % Mod;
NTT(f, len, -1), cpy(res, f, n + 1);
}
P5396 第二类斯特林数·列
P5408 第一类斯特林数·行
第 \(n\) 行第一类斯特林数的 OGF 为:
考虑倍增,由于:
即:
问题转化为已知多项式 \(F\) 时快速计算 \(F(x + k)\) ,推式子:
直接做差卷积即可,由主定理得时间复杂度 \(O(n \log n)\) 。
inline void calc(int *f, int n, int k, int *res) {
static int a[N], b[N];
a[0] = 1, b[0] = 0;
for (int i = 1; i <= n; ++i)
a[i] = 1ll * a[i - 1] * k % Mod, b[i] = 1ll * f[i] * fac[i] % Mod;
for (int i = 2; i <= n; ++i)
a[i] = 1ll * a[i] * invfac[i] % Mod;
reverse(a, a + n + 1);
int len = calc((n + 1) << 1);
clr(a + n + 1, len - n - 1), clr(b + n + 1, len - n - 1);
NTT(a, len, 1), NTT(b, len, 1);
for (int i = 0; i < len; ++i)
a[i] = 1ll * a[i] * b[i] % Mod;
NTT(a, len, -1);
for (int i = 0; i <= n; ++i)
res[i] = 1ll * a[i + n] * invfac[i] % Mod;
}
inline void solve(int n, int *res) {
static int f[N], g[N], sta[N];
int top = 0;
while (n)
sta[++top] = n, n >>= 1;
clr(f, n << 1);
f[0] = 0, n = f[1] = sta[top--];
while (top--) {
calc(f, n, n, g);
int len = calc((n + 1) << 1);
clr(f + n + 1, len - n - 1), clr(g + n + 1, len - n - 1);
NTT(f, len, 1), NTT(g, len, 1);
for (int i = 0; i < len; ++i)
f[i] = 1ll * f[i] * g[i] % Mod;
NTT(f, len, -1), n <<= 1;
if (n != sta[top + 1]) {
f[n + 1] = f[n];
for (int i = n; i; --i)
f[i] = add(f[i - 1], 1ll * f[i] * n % Mod);
f[0] = 1ll * f[0] * n % Mod, ++n;
}
}
cpy(res, f, n + 1);
}
P5409 第一类斯特林数·列
反转公式
公式一
证明:
公式二
证明:
斯特林反演
证明:
应用
CF932E Team Work
给定 \(n, k\) ,求:
\[\left( \sum_{i = 1}^n \binom{n}{i} \times i^k \right) \bmod (10^9 + 7) \]\(n \leq 10^9\) ,\(k \leq 5000\)
推式子:
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 5e3 + 7;
int S[N][N];
int n, k;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
signed main() {
scanf("%d%d", &n, &k);
S[0][0] = 1;
for (int i = 1; i <= k; ++i)
for (int j = 1; j <= i; ++j)
S[i][j] = add(S[i - 1][j - 1], 1ll * j * S[i - 1][j] % Mod);
int ans = 0;
for (int i = 0, mul = 1; i <= min(n, k); mul = 1ll * mul * (n - i) % Mod, ++i)
ans = add(ans, 1ll * S[k][i] * mul % Mod * mi(2, n - i) % Mod);
printf("%d", ans);
return 0;
}
P4091 [HEOI2016/TJOI2016] 求和
给定 \(n\) ,求:
\[\left( \sum_{i = 0}^n \sum_{j = 0}^i {i \brace j} \times 2^j \times j! \right) \bmod 998244353 \]\(n \leq 10^5\)
考虑第二类斯特林数的性质:
二项式反演得到:
带入所求式:
记 \(a_k = \sum_{i = 0}^n k^i\) ,则原式可以化为:
不难 NTT 做到 \(O(n \log n)\) ,但是还可以更优:
记 \(b_j = \sum_{i = j}^n \binom{i}{j} q^{i - j}\) ,考虑线性求出 \(b\) :
先算前面的式子:
再算后面的式子:
因此:
解得:
因此可以线性求解,其中 \(a\) 需要用线性筛。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 1e5 + 7;
int fac[N], inv[N], invfac[N], pw[N], pri[N], val[N];
int a[N], b[N];
bool isp[N];
int n, pcnt;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline int sgn(int n) {
return n & 1 ? Mod - 1 : 1;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
pw[0] = 1;
for (int i = 1; i <= n; ++i)
pw[i] = 2ll * pw[i - 1] % Mod;
memset(isp, true, sizeof(isp));
isp[1] = false, val[1] = 1;
for (int i = 2; i <= n; ++i) {
if (isp[i])
pri[++pcnt] = i, val[i] = mi(i, n + 1);
for (int j = 1; j <= pcnt && i * pri[j] <= n; ++j) {
pri[i * pri[j]] = false, val[i * pri[j]] = 1ll * val[i] * val[pri[j]] % Mod;
if (!(i % pri[j]))
break;
}
}
}
inline int C(int n, int m) {
return m > n ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
signed main() {
scanf("%d", &n);
prework(n);
b[0] = 1ll * dec(1, 1ll * sgn(n + 1) * val[2] % Mod) * inv[3] % Mod;
for (int i = 1; i <= n; ++i)
b[i] = 1ll * inv[3] * dec(b[i - 1], 1ll * add(C(n, i), C(n, i - 1)) *
sgn(n - i + 1) % Mod * pw[n - i + 1] % Mod) % Mod;
for (int i = 0; i <= n; ++i)
a[i] = (i <= 1 ? i * n + 1 : 1ll * dec(val[i], 1) * inv[i - 1] % Mod);
int ans = 0;
for (int i = 0; i <= n; ++i)
ans = add(ans, 1ll * a[i] * pw[i] % Mod * b[i] % Mod);
printf("%d", ans);
return 0;
}
P4827 [集训队互测 2011] Crash 的文明世界
给定一棵 \(n\) 个点的树,对于每个点 \(x\) ,求:
\[\left( \sum_{i = 1}^n \mathrm{dist}(x, i)^k \right) \bmod 10007 \]\(n \leq 5 \times 10^4\) ,\(k \leq 150\)
先将 \(k\) 次幂用斯特林数化掉:
设 \(f_{i, j}\) 表示 \(i\) 子树内 \(j\) 的答案,组合数不难用加法公式递推,得到:
最后做一遍换根 DP 即可做到 \(O(nk)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e4 + 7;
const int N = 5e4 + 7, K = 1.5e2 + 7;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
int f[N][K], g[N][K], h[K], S[K][K], fac[K];
int n, k;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline void prework() {
fac[0] = fac[1] = 1;
for (int i = 2; i <= k; ++i)
fac[i] = 1ll * fac[i - 1] * i % Mod;
S[0][0] = 1;
for (int i = 1; i <= k; ++i)
for (int j = 1; j <= i; ++j)
S[i][j] = add(S[i - 1][j - 1], 1ll * j * S[i - 1][j] % Mod);
}
void dfs1(int u, int fa) {
g[u][0] = 1;
for (int v : G.e[u]) {
if (v == fa)
continue;
dfs1(v, u);
for (int i = 0; i <= k; ++i)
g[u][i] = add(g[u][i], add(g[v][i], i ? g[v][i - 1] : 0));
}
}
void dfs2(int u, int fa) {
memcpy(f[u], g[u], sizeof(int) * (k + 1));
if (fa) {
h[0] = dec(f[fa][0], g[u][0]);
for (int i = 1; i <= k; ++i)
h[i] = dec(f[fa][i], add(g[u][i], g[u][i - 1]));
f[u][0] = add(f[u][0], h[0]);
for (int i = 1; i <= k; ++i)
f[u][i] = add(f[u][i], add(h[i], h[i - 1]));
}
for (int v : G.e[u])
if (v != fa)
dfs2(v, u);
}
signed main() {
scanf("%d%d", &n, &k);
prework();
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G.insert(u, v), G.insert(v, u);
}
dfs1(1, 0), dfs2(1, 0);
for (int i = 1; i <= n; ++i) {
int ans = 0;
for (int j = 0; j <= k; ++j)
ans = add(ans, 1ll * S[k][j] * fac[j] % Mod * f[i][j] % Mod);
printf("%d\n", ans);
}
return 0;
}
CF960G Bandit Blues
给定 \(n, a, b\) ,求长度为 \(n\) 的排列中前缀最大值数量为 \(a\) 、后缀最大值数量为 \(b\) 的排列数量 \(\bmod 998244353\) 。
\(n \leq 10^5\)
记 \(p_i = n\) ,则前缀最大值的位置 \(\in [1, i]\) ,后缀最大值的位置 \(\in [i, n]\) ,因此可以化子问题。设 \(f_{i, j}\) 表示长度为 \(j\) 的排列,前缀最大值有 \(j\) 个的方案数,答案即为:
不难发现 \(f_{i, j} = {i \brack j}\) ,只要选每个置换环的最大值放在最前面即可。
考虑答案的组合意义,枚举所有 \(1 \leq i \leq n\) ,先从 \(n - 1\) 个球中选出 \(i - 1\) 个,然后将它们组成 \(a - 1\) 个环,再将剩下 \(n - i\) 个球组成 \(b - 1\) 个环的方案数。
考虑将 \(n - 1\) 个球分为 \(a + b - 2\) 个环,再将这些环分到左边或右边,不难发现这与前面的方案意义对应,因此答案即为:
signed main() {
prework();
scanf("%d%d%d", &n, &a, &b);
if (!a || !b || a + b - 1 > n)
return puts("0"), 0;
else if (n == 1)
return puts("1"), 0;
solve(n - 1, f);
printf("%d", 1ll * f[a + b - 2] * C(a + b - 2, a - 1) % Mod);
return 0;
}
CF961G Partitions
给定 \(n\) 个物品,第 \(i\) 个物品有权值 \(w_i\) 。
- 定义集合 \(S\) 的权值为 \(W(S) = |S| \times \sum_{x \in S} w_x\) 。
- 定义划分 \(R\) 的权值为 \(W'(R) = \sum_{S \in R} W(S)\) 。
求将这 \(n\) 个物品划分为 \(k\) 个集合的所有方案的权值和 \(\bmod (10^9 + 7)\) 。
\(n, k \leq 2 \times 10^5\)
考虑 \(W(S) = |S| \times \sum_{x \in S} w_x\) 的组合意义,相当于一个 \(i \in S\) 被所有的 \(j \in S\) 统计了贡献。
因此可以考虑拆贡献,对于一对 \(i, j\) 计算贡献。若 \(i = j\) ,则方案数为 \(n \brace k\) ,否则方案数为 \(n - 1 \brace k\) 。答案即为:
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 2e5 + 7;
int fac[N], inv[N], invfac[N];
int n, k;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline int sgn(int n) {
return n & 1 ? Mod - 1 : 1;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int calc(int n, int m) {
int res = 0;
for (int i = 0; i <= m; ++i)
res = add(res, 1ll * sgn(m - i) * mi(i, n) % Mod * invfac[i] % Mod * invfac[m - i] % Mod);
return res;
}
signed main() {
scanf("%d%d", &n, &k);
int ans = 0;
for (int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
ans = add(ans, x);
}
prework(n);
printf("%d", 1ll * ans * add(calc(n, k), 1ll * (n - 1) * calc(n - 1, k) % Mod) % Mod);
return 0;
}
CF1644F Basis
有两种对于数组的变换:
- \(F(a, k)\) :将数组 \(a\) 的每一个数替换成 \(k\) 个该数后,取前 \(|a|\) 个数。
- \(G(a, x, y)\) :将数组 \(a\) 中每一个 \(x\) 替换成 \(y\) ,同时将每一个 \(y\) 替换成 \(x\) 。
有 \(m\) 个长度为 \(n\) 、元素为 \(1 \sim k\) 之间整数的数组构成集合 \(S\) ,满足对于所有长度为 \(n\) 、元素为 \(1 \sim k\) 之间整数的数组,均存在 \(a \in S\) 使得 \(a\) 经过若干次变化后得到该数组,求 \(m\) 的最小值 \(\bmod 998244353\) 。
\(n, k \leq 2 \times 10^5\)
先考虑只有 \(G\) 的情况,不难发现 \(G\) 不会改变数字出现次数组成的可重集,枚举数字种数可得答案即为 \(\sum_{i = 1}^{\min(n, k)} {n \brace i}\) ,不难做到 \(O(n \log n)\) 。
再考虑 \(F(a, k)\) 变换后的数组形态,不难发现对于除了最后一段的所有连续段,若长度的 \(\gcd = 1\) ,则其不能被 \(F\) 变换得到,否则取 \(k = \gcd\) 即可。
\(\gcd = 1\) 是困难的,考虑容斥,设 \(f(x)\) 表示极长段(除去最后一段)长度都为 \(i\) 的倍数时的方案数,答案即为 \(\sum \mu(i) f(i)\) ,\(f\) 即为 \(\lceil \frac{n}{j} \rceil\) 个数的数组含有 \(i\) 个不同数的方案数,不难 \(O(n \log n)\) 求得。
时间复杂度 \(O(n \log n \ln n)\) 。
signed main() {
prework();
scanf("%d%d", &n, &m), m = min(m, n);
solve(n, f);
if (m >= 2)
f[1] = 0;
fill(g + 2, g + n + 1, Mod - 1);
for (int i = 2; i <= n; ++i) {
solve((n + i - 1) / i, val);
for (int j = 2; j <= (n + i - 1) / i; ++j)
f[j] = add(f[j], 1ll * g[i] * val[j] % Mod);
for (int j = i * 2; j <= n; j += i)
g[j] = dec(g[j], g[i]);
}
int ans = 0;
for (int i = 1; i <= m; ++i)
ans = add(ans, f[i]);
printf("%d", ans);
return 0;
}
LOJ2834. 「JOISC 2018 Day 2」修行
对于一个排列 \(p\) ,定义其最小划分为一个划分满足每一段递增。
给出 \(n, k\) ,求有多少长度为 \(n\) 的排列最小划分为 \(k\) ,答案对 \(10^9 + 7\) 取模。
\(n \leq 10^5\)
最小划分这个结构并不好刻画,考虑将最小划分为 \(k\) 转化为有 \(n - k\) 个 \(i\) 满足 \(p_i < p_{i + 1}\) 。
考虑二项式反演,设至少 \(k\) 个 \(p_i < p_{i + 1}\) 的方案数为 \(f(k)\) ,恰好 \(k\) 个 \(p_i < p_{i + 1}\) 的方案数为 \(g(k)\) ,则:
先考虑求 \(f(i)\) ,其相当于将 \(n\) 个数划分为 \(n - i\) 个互相区分的集合,每个集合内部排序后拼接即可满足,即:
接下来考虑求解 \(g(k)\) ,将第二类斯特林数展开带入二项式反演得到:
答案即为 \(g(n - k)\) ,线性筛预处理 \(j^n\) 可以做到 \(O(n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 1e5 + 7;
int fac[N], inv[N], invfac[N], pri[N], pw[N];
bool isp[N];
int n, k, pcnt;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline int sgn(int n) {
return n & 1 ? Mod - 1 : 1;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline void sieve(int n) {
memset(isp + 1, true, sizeof(bool) * n);
isp[1] = false, pw[1] = 1;
for (int i = 2; i <= n; ++i) {
if (isp[i])
pri[++pcnt] = i, pw[i] = mi(i, n);
for (int j = 1; j <= pcnt && i * pri[j] <= n; ++j) {
isp[i * pri[j]] = false, pw[i * pri[j]] = 1ll * pw[i] * pw[pri[j]] % Mod;
if (!(i % pri[j]))
break;
}
}
}
inline int C(int n, int m) {
return m > n ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
signed main() {
scanf("%d%d", &n, &k);
prework(n + 1), sieve(n), k = n - k;
int ans = 0;
for (int i = 0; i <= n - k; ++i)
ans = add(ans, 1ll * sgn(n - k - i) * pw[i] % Mod * C(n + 1, i + k + 1) % Mod);
printf("%d", ans);
return 0;
}