斯特林数

斯特林数

第二类斯特林数

第二类斯特林数(斯特林子集数) 表示将 \(n\) 个两两不同的元素,划分为 \(k\) 个互不区分的非空子集的方案数,记作 \(n \brace k\)

递推式

\[{n \brace k} = {n - 1 \brace k - 1} + k {n - 1 \brace k} \\ {n \brace 0} = [n = 0] \]

组合意义:插入一个新元素时有两种方案:将新元素单独放入一个子集,或将新元素放入一个现有的非空子集。

通项公式

\[{n \brace m} = \sum_{i = 0}^m \dfrac{(-1)^{m - i} i^n} {i! (m - i)!} \]

使用容斥原理证明该公式。设:

  • \(G_m\) 表示将 \(n\) 个两两不同的元素,划分到 \(m\) 个两两不同的集合(允许空集)的方案数,则 \(G_m = m^n\)
  • \(F_m\) 表示将 \(n\) 个两两不同的元素,划分到 \(m\) 个两两不同的非空集合(不允许空集)的方案数。

不难得到 \(G_m = \sum_{i = 0}^m \binom{m}{i} F_i\) ,二项式反演得到:

\[\begin{aligned} F_m &= \sum_{i = 0}^m (-1)^{m - i} \dbinom{m}{i} G_i \\ &= \sum_{i = 0}^m (-1)^{m - i} \dbinom{m}{i} i^n \\ &= \sum_{i = 0}^m \dfrac{m! (-1)^{m - i} i^n} {i! (m - i)!} \end{aligned} \]

考虑 \(F_i\)\(n \brace i\) 的关系,第二类斯特林数要求集合之间互不区分,因此 \(F_i\) 正好就是 \(n \brace i\)\(i!\) 倍。

性质

\[m^n = \sum_{i = 0}^m \binom{m}{i} {n \brace i} i! = \sum_{i = 0}^n {n \brace i} m^{\underline{i}} \]

组合意义:\(n\) 个带标号小球放进 \(m\) 个带标号盒子(允许空盒子)的方案数。

注意这里规定 \(0^0 = 1\)

第一类斯特林数

第一类斯特林数(斯特林轮换数)表示将 \(n\) 个两两不同的元素,划分为 \(k\) 个互不区分的非空轮换的方案数,记作 \({n \brack k}\)

递推式

\[{n \brack k} = {n - 1 \brack k - 1} + (n - 1) {n - 1 \brack k} \\ {n \brack 0} = [n = 0] \]

组合意义:插入一个新元素时有两种方案:将该新元素置于一个单独的轮换中,或将该元素插入到任何一个现有的轮换中。

幂之间的转化

记上升幂 \(x^{\overline{n}} = \prod_{k = 0}^{n - 1} (x + k)\) ,下降幂 \(x^{\underline{n}} = \prod_{k = 0}^{n - 1} (x - k)\) ,则:

\[\begin{align} x^{\overline{n}} &= \sum_{i = 0}^n {n \brack i} x^i \\ x^{\underline{n}} &= \sum_{i = 0}^n {n \brack i} (-1)^{n - i} x^i \\ x^n &= \sum_{k = 0}^n {n \brace k}x^{\underline{k}} = \sum_{k = 0}^n {n \brace k} (-1)^{n - k} x^{\overline{k}} \\ \end{align} \]

求行/列的斯特林数

P5395 第二类斯特林数·行

\(f(m)= m^n, g(m) = {n \brace m} m!\) ,则:

\[f(m) = \sum_{i = 0}^m \binom{m}{i} g(i) \]

二项式反演得到:

\[\begin{aligned} g(m) &= \sum_{i = 0}^m (-1)^{m - i} \binom{m}{i} f(i) \\ &= \sum_{i = 0}^m (-1)^{m - i} \frac{m!}{i! (m - i)!} i^n \\ &= m! \sum_{i = 0}^m \frac{(-1)^{m - i}}{(m - i)!} \frac{i^n}{i!} \\ \end{aligned} \]

直接卷积即可做到 \(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 为:

\[x^{\overline{n}} = \sum_{i = 0}^n {n \brack i} x^i = \prod_{i = 0}^{n - 1} (x + i) \]

考虑倍增,由于:

\[x^{\overline{2n}} = x^{\overline{n}} (x + n)^{\overline{n}} \]

即:

\[\prod_{i = 0}^{2n - 1} (x + i) = \left( \prod_{i = 0}^{n - 1} (x + i) \right) \left( \prod_{i = 0}^{n - 1} (x + n + i) \right) \]

问题转化为已知多项式 \(F\) 时快速计算 \(F(x + k)\) ,推式子:

\[\begin{aligned} F(x + k) &= \sum_{i = 0}^n f_i (x + k)^i \\ &= \sum_{i = 0}^n f_i \sum_{j = 0}^i \binom{i}{j} x^j k^{i - j} \\ &= \sum_{i = 0}^n x^i \sum_{j = i}^n f_j \binom{j}{i} k^{j - i} \\ &= \sum_{i = 0}^n \frac{x^i}{i!} \sum_{j = i}^n f_j j! \times \frac{k^{j - i}}{(j - i)!} \\ \end{aligned} \]

直接做差卷积即可,由主定理得时间复杂度 \(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 第一类斯特林数·列

反转公式

公式一

\[\sum_{k = m}^n (-1)^{n - k} {n \brack k} {k \brace m} = [m = n] \]

证明:

\[\begin{aligned} m^{\underline{n}} &= \sum_{i = 0}^n {n \brack i} (-1)^{n - i} m^i \\ &= \sum_{i = 0}^n {n \brack i} (-1)^{n - i} \sum_{j = 0}^i {i \brace j} m^{\underline{j}} \\ &= \sum_{i = 0}^n m^{\underline{i}} \sum_{j = i}^n (-1)^{n - j} {n \brack j} {j \brace i} \end{aligned} \]

公式二

\[\sum_{k = m}^n (-1)^{n - k} {n \brace k} {k \brack m} = [m = n] \]

证明:

\[\begin{aligned} m^n &= \sum_{i = 0}^n {n \brace i} m^{\underline{i}} \\ &= \sum_{i = 0}^n {n \brace i} (-1)^i (-m)^{\overline{i}} \\ &= \sum_{i = 0}^n {n \brace i} (-1)^i \sum_{j = 0}^i {i \brack j} (-m)^j \\ &= \sum_{i = 0}^n m^i \sum_{j = i}^n (-1)^{i - j} {n \brace j} {j \brack i} \end{aligned} \]

斯特林反演

\[f(n) = \sum_{k = 0}^n {n \brace k} g(k) \iff g(n) = \sum_{k = 0}^n (-1)^{n - k} {n \brack k} f(k) \]

证明:

\[\begin{aligned} f(n) &= \sum_{k = 0}^n [k = n] f(k) \\ &= \sum_{k = 0}^n \sum_{j = k}^n {n \brace k} {j \brack k} (-1)^{j - k} f(k) \\ &= \sum_{k = 0}^n {n \brace k} \sum_{j = 0}^k (-1)^{k - j} {k \brack j} f(j) \\ &= \sum_{k = 0}^n {n \brace k} g(k) \end{aligned} \]

应用

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\)

推式子:

\[\begin{aligned} \sum_{i = 1}^n \binom{n}{i} \times i^k &= \sum_{i = 1}^n \binom{n}{i} \sum_{j = 0}^i \binom{i}{j} {k \brace j} j! \\ &= \sum_{i = 1}^n \frac{n!}{(n - i)!} \sum_{j = 0}^i \frac{k \brace j}{(i - j)!} \\ &= \sum_{i = 0}^{\min(n, k)} {k \brace i} \sum_{j = i}^n \frac{n!}{(n - j)! (j - i)!} \\ &= \sum_{i = 0}^{\min(n, k)} {k \brace i} \frac{n!}{(n - i)!} \sum_{j = i}^n \binom{n - i}{j - i} \\ &= \sum_{i = 0}^{\min(n, k)} {k \brace i} \frac{n!}{(n - i)!} 2^{n - i} \\ \end{aligned} \]

#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\)

考虑第二类斯特林数的性质:

\[m^n = \sum_{i = 0}^m \binom{m}{i} {n \brace i} i! \]

二项式反演得到:

\[{n \brace m} m! = \sum_{i = 0}^m (-1)^{m - i} \binom{m}{i} i^n \]

带入所求式:

\[\begin{aligned} & \sum_{i = 0}^n \sum_{j = 0}^i {i \brace j} \times 2^j \times j! \\ =& \sum_{i = 0}^n \sum_{j = 0}^n 2^j \sum_{k = 0}^j (-1)^{j - k} \binom{j}{k} k^i \\ =& \sum_{j = 0}^n 2^j \sum_{k = 0}^j (-1)^{j - k} \binom{j}{k} \sum_{i = 0}^n k^i \\ \end{aligned} \]

\(a_k = \sum_{i = 0}^n k^i\) ,则原式可以化为:

\[\sum_{j = 0}^n 2^j j! \sum_{k = 0}^j \frac{(-1)^{j - k}}{(j - k)!} \frac{a_k}{k!} \]

不难 NTT 做到 \(O(n \log n)\) ,但是还可以更优:

\[\begin{aligned} &= \sum_{j = 0}^n 2^j \sum_{k = 0}^j (-1)^{j - k} \binom{j}{k} a_k \\ &= \sum_{k = 0}^n a_k \sum_{j = k}^n 2^j (-1)^{j - k} \binom{j}{k} \\ &= \sum_{k = 0}^n a_k 2^k \sum_{j = k}^n (-2)^{j - k} \binom{j}{k} \end{aligned} \]

\(b_j = \sum_{i = j}^n \binom{i}{j} q^{i - j}\) ,考虑线性求出 \(b\)

\[\begin{aligned} b_j &= \sum_{i = j}^n \binom{i}{j} q^{i - j} \\ &= \left( \sum_{i = j}^n \binom{i - 1}{j} q^{i - j} \right) + \left( \sum_{i = j}^n \binom{i - 1}{j - 1} q^{i - j} \right) \\ \end{aligned} \]

先算前面的式子:

\[\begin{aligned} & \sum_{i = j}^n \binom{i - 1}{j} q^{i - j} \\ =& q \sum_{i = j}^n \binom{i - 1}{j} q^{i - j - 1} \\ =& q \sum_{i = j}^{n - 1} \binom{i}{j} q^{i - j} \\ =& q \left( b_j - \binom{n}{j} q^{n - j} \right) \\ \end{aligned} \]

再算后面的式子:

\[\begin{aligned} & \sum_{i = j}^n \binom{i - 1}{j - 1} q^{i - j} \\ =& \sum_{i = j - 1}^{n - 1} \binom{i}{j - 1} q^{i - (j - 1)} \\ =& b_{j - 1} - \binom{n}{j - 1} q^{n - (j - 1)} \end{aligned} \]

因此:

\[b_j = q \left( b_j - \binom{n}{j} q^{n - j} \right) + b_{j - 1} - \binom{n}{j - 1} q^{n - (j - 1)} \]

解得:

\[b_j = \frac{1}{1 - q} \left(b_{j - 1} - \left( \binom{n}{j - 1} + \binom{n}{j} \right) q^{n - j + 1} \right) \]

因此可以线性求解,其中 \(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\) 次幂用斯特林数化掉:

\[\begin{aligned} F(x) &= \sum_{i = 1}^n \mathrm{dist}(x, i)^k \\ &= \sum_{i = 1}^n \sum_{j = 0}^k \binom{\mathrm{dist}(x, i)}{j} {k \brace j} j! \end{aligned} \]

\(f_{i, j}\) 表示 \(i\) 子树内 \(j\) 的答案,组合数不难用加法公式递推,得到:

\[f_{u, i} = \sum_{v \in son(u)} f_{v, i} + f_{v, i - 1} \]

最后做一遍换根 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\) 个的方案数,答案即为:

\[\sum_{i = 1}^n f_{i - 1, a - 1} \times f_{n - i, b - 1} \times \binom{n - 1}{i - 1} \]

不难发现 \(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\) 个环,再将这些环分到左边或右边,不难发现这与前面的方案意义对应,因此答案即为:

\[{n - 1 \brack a + b - 2} \binom{a + b - 2}{a - 1} \]

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\) 。答案即为:

\[\sum_{i = 1}^n w_i \left( {n \brace k} + (n - 1) {n - 1 \brace k} \right) \]

#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)\) ,则:

\[g(k) = \sum_{i = k}^n (-1)^{i - k} \binom{i}{k} f(i) \]

先考虑求 \(f(i)\) ,其相当于将 \(n\) 个数划分为 \(n - i\) 个互相区分的集合,每个集合内部排序后拼接即可满足,即:

\[f(i) = {n \brace n - i} (n - i)! \]

接下来考虑求解 \(g(k)\) ,将第二类斯特林数展开带入二项式反演得到:

\[\begin{align} g(k) &= \sum_{i = k}^n (-1)^{i - k} \binom{i}{k} {n \brace n - i} (n - i)! \\ &= \sum_{i = k}^n (-1)^{i - k} \binom{i}{k} (n - i)! \sum_{j = 0}^{n - i} \frac{(-1)^{n - i - j} j^n}{j! (n - i - j)!} \\ &= \sum_{i = k}^n (-1)^{i - k} \binom{i}{k} \sum_{j = 0}^{n - i} (-1)^{n - i - j} j^n \binom{n - i}{j} \\ &= \sum_{i = k}^n \sum_{j = 0}^{n - i} (-1)^{n - k - j} j^n \binom{i}{k} \binom{n - i}{j} \\ &= \sum_{j = 0}^{n - k} (-1)^{n - k - j} j^n \sum_{i = k}^{n - j} \binom{i}{k} \binom{n - i}{j} \\ &= \sum_{j = 0}^{n - k} (-1)^{n - k - j} j^n \binom{n + 1}{j + k + 1} \end{align} \]

答案即为 \(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;
}

posted @ 2025-05-20 14:38  wshcl  阅读(33)  评论(0)    收藏  举报