群论杂记

群论杂记

群的定义与性质

群的定义:群 \((S, \cdot)\) 是由集合 \(S\) 和二元运算 \(\cdot\) 构成的代数结构,满足以下性质:

  • 封闭性:\(\forall a, b \in S, a \cdot b \in S\)
  • 结合律:\(\forall a, b, c \in S, (a \cdot b) \cdot c = a \cdot (b \cdot c)\)
  • 单位元:\(\exist e \in S, \forall a \in S, e \cdot a = a \cdot e = a\)
  • 逆元:\(\forall a \in S, \exist b \in S, a \cdot b = b \cdot a = e\) ,称 \(b\)\(a\) 的逆元,记为 \(a^{-1}\)

不引起歧义时,可以用 \(S\) 代替 \((S, \cdot)\) 表示群。

相关定义:

  • 阿贝尔群(交换群):满足交换律(\(\forall a, b \in S, a \cdot b = b \cdot a\))的群。
  • 半群:由集合 \(S\) 和二元运算 \(\cdot\) 构成的代数结构 \((S, \cdot)\) ,满足封闭性、结合律。
  • 幺半群:由集合 \(S\) 和二元运算 \(\cdot\) 构成的代数结构 \((S, \cdot)\) ,满足结合律、存在单位元。
  • 阶:群的元素个数,记为 \(|S|\) 。按照阶的有限性可以将群划分为有限群和无限群。

群的简单性质:

  • 单位元唯一。

    证明:若存在两个不同的单位元 \(e_1, e_2\) ,则 \(e_1 = e_1 e_2 = e_2\) ,矛盾。

  • \(a \cdot x = e\) ,则称 \(a\)\(x\) 的左逆元;若 \(x \cdot b = e\) ,则称 \(b\)\(x\) 的右逆元。则在一个群中,左逆元与右逆元相同。

    证明:若 \(a \cdot x = e\) ,记 \(c \cdot a = e\) ,则 \(x \cdot a = (c \cdot a) \cdot (x \cdot a) = c \cdot (a \cdot x) \cdot a = c \cdot a = e\)

  • 逆元唯一。

    证明:若 \(x\) 存在两个不同的逆元 \(a, b\) ,则 \(a = a \cdot x \cdot b = b\)

  • 消去律:\(\forall a, b, x \in S, a \cdot x = b \cdot x \iff a = b\)

    证明:两边同乘 \(x^{-1}\) 即可。

子群相关

定义:

  • 子群:对于群 \((S, \cdot)\),如果 \(S' \subseteq S\)\((S', \cdot)\) 也构成群,则称群 \((S', \cdot)\) 是群 \((S, \cdot)\) 的子群,记作 \(S’ \leq S\)
  • 陪集:对于群 \(G\) 的一个子集 \(H\) ,若 \(H \leq G\) ,对于 \(a \in G\) ,定义 \(H\) 的一个左陪集为 \(_a H = \{ ah \mid h \in H \}\) ,定义 \(H\) 的一个右陪集为 \(H_a = \{ ha \mid h \in H \}\)
    • 注意陪集不一定是群,因为其可能没有单位元。

陪集的简单性质(以右陪集为例,左陪集同理):

  • \(\forall a \in G, |H| = |H_a|\)

    证明:若 \(h_1 \neq h_2 \in S\) ,则 \(h_1 a \neq h_2 a\) 。因此对于不同的 \(h\)\(ha\) 互异,因此 \(|H| = |H_a|\)

  • \(\forall a \in G, a \in H_a\)

    证明:考虑 \(H\) 的单位元 \(e\) ,则 \(a = ea \in H_a\)

  • \(H_a = H \iff a \in H\)

    充分性:\(a = ea \in H_a = H\)

    必要性:由封闭性得 \(H_a \subseteq H\) ,而 \(|H| = |H_a|\) ,因此 \(H_a = H\)

  • \(H_a = H_b \iff a b^{-1} \in H\)

    充分性:\(a \in H_a \Rightarrow a \in H_b \Rightarrow a b^{-1} \in H\)

    必要性:\(H_{b a^{-1}} = H \Rightarrow H_a = H_b\)

  • \(H_a \cap H_b \neq \empty \Rightarrow H_a = H_b\) ,即 \(H\) 的的陪集要么相等、要么不交。

    证明:考虑 \(c \in H_a \cap H_b\) ,则 \(\exist h_1, h_2 \in H, h_1 a = h_2 b = c\) ,则 \(a b^{-1} = h_1^{-1} h_2 \in H\) ,因此 \(H = H_{a b^{-1}} \iff H_a = H_b\)

拉格朗日定理:若 \(H \leq G\) ,则:

\[|G| = |H| \times [G : H] \]

其中 \([G : H]\) 表示 \(G\)\(H\) 不同的陪集数。

证明:\(H\) 的的陪集要么相等、要么不交。

置换群

置换的定义:有限集合到自身的双射。对于不可重集合 \(S = \{ a_1, a_2, \cdots, a_n \}\) 上的置换 \(f\) 可以表示为:

\[f = \begin{pmatrix} a_1 & a_2 & \cdots & a_n \\ a_{p_1} & a_{p_2} & \cdots & a_{p_n} \end{pmatrix} \]

\(f(a_i) = a_{p_i}\) ,其中 \(p_{1 \sim n}\)\(1 \sim n\) 的一个排列。

对于两个置换 \(f = \begin{pmatrix}a_{p_1} & a_{p_2} & \cdots & a_{p_n} \\ a_{q_1} & a_{q_2} & \cdots & a_{q_n} \end{pmatrix}\)\(g = \begin{pmatrix} a_1 & a_2 & \cdots & a_n \\ a_{p_1} & a_{p_2} & \cdots & a_{p_n} \end{pmatrix}\) ,定义置换的乘法:

\[f \circ g = \begin{pmatrix} a_1 & a_2 & \cdots & a_n \\ a_{q_1} & a_{q_2} & \cdots & a_{q_n} \end{pmatrix} \]

\((f \circ g)(x) = f(g(x))\)

不难发现集合 \(S\) 上的所有置换与置换的乘法构成的结构满足封闭性、结合律、单位元(恒等置换:每个元素映射成它自己)、逆元(交换置换上下两行),因此可以构成群,称之为置换群。

轨道-稳定子定理

定义 \(A, B\) 是两个有限集合,\(X = B^A\) 表示所有 \(A \to B\) 的映射,\(G\) 是作用在 \(A\) 上的置换群。

对于每个 \(x \in X\) ,定义:

  • 轨道:\(G(x) = \{ g(x) \mid g \in G \}\)
  • 稳定子:\(G^x = \{ g \mid g(x) = x, g \in G \}\)

例子:考虑环染色问题:

  • \(A\) 就是环上元素的集合,\(B\) 就是所有颜色的集合,\(X\) 就是不考虑本质不同方案的集合(\(|X| = |B|^{|A|}\))。
  • \(G\) 就是旋转操作,\(g \in G\) 可以视为顺时针旋转多少步。
  • \(G(x)\) 就是 \(x\) 这个映射经过 \(G\) 中所有置换后本质不同的映射集合。
  • \(G^x\) 就是 \(G\) 中使得 \(x\) 这个映射置换后不变的置换集合。

轨道-稳定子定理:

\[|G| = |G(x)| \times |G^x| \]

证明:首先证明 \(G^x\)\(G\) 的一个子群,这是因为:

  • 封闭性:对于 \(f, g \in G\) ,则 \(f(x) = g(x) = x\) ,因此 \((f \circ g)(x) = x \in G^x\)
  • 结合律:显然置换的乘法满足结合律。
  • 单位元:恒等变换。
  • 逆元:对于 \(g \in G^x\) ,则 \(g^{-1}(x) = g^{-1}(g(x)) = (g^{-1} \circ g)(x) = I(x) = x\) ,因此 \(g^{-1} \in G^x\)

由拉格朗日定理得 \(|G| = |G^x| \times [G : G^x]\) ,于是只要证明 \(|G(x)| = [G : G^x]\) 即可,而这只要证明每一个 \(g(x)\) 都能与 \([G : G^x]\) 中的一个左/右陪集对应,对每一个 \(g(x)\)\(_g G^x\) 表示它对应的陪集即可。

Burnside 引理

在轨道-稳定子定理的前置定义下再定义:

  • \(X / G\) 表示 \(G\) 作用在 \(X\) 上产生的所有等价类集合(若 \(X\) 中的两个映射经过 \(G\) 中的置换后相等,则它们属于同一等价类),即不同轨道的集合,因此称 \(|X / G|\)\(X\) 关于 \(G\) 的轨道数。
  • \(X^g\)\(X\) 在置换 \(g\) 下的不动点集合,即 \(X^g = \{ x \mid g(x) = x, x \in X \}\)

例子:同样考虑环染色问题:

  • \(X / G\) 就是不能通过 \(G\) 中的旋转操作变得相同的映射集合。
  • \(X^g\) 就是对于 \(g\) 这个置换,对应位置颜色不变的位置集合。

Burnside 引理:

\[|X / G| = \frac{1}{|G|} \sum_{g \in G} |X^g| \]

证明:

\[\begin{aligned} \frac{1}{|G|} \sum_{g \in G} |X^g| &= \frac{1}{|G|} \sum_{g \in G} \sum_{x \in X} [g(x) = x] \\ &= \frac{1}{|G|} \sum_{x \in X} \sum_{g \in G} [g(x) = x] \\ &= \frac{1}{|G|} \sum_{x \in X} |G^x| \\ &= \frac{1}{|G|} \sum_{x \in X} \frac{|G|}{|G(x)|} \\ &= \sum_{x \in X} \frac{1}{|G(x)|} \\ &= \sum_{Y \in X / G} \sum_{x \in Y} \frac{1}{|G(x)|} \\ \end{aligned} \]

发现对于等价类里得所有元素 \(y\) ,必然存在一个 \(g \in G\) 满足 \(g(x) = y\) ,因此 \(|G(x)| = |Y|\) ,于是:

\[\begin{aligned} \sum_{Y \in X / G} \sum_{x \in Y} \frac{1}{|G(x)|} &= \sum_{Y \in X / G} \sum_{x \in Y} \frac{1}{|Y|} \\ &= \sum_{Y \in X / G} 1 \\ &= |X / G| \end{aligned} \]

可以发现证明过程并没有用到 \(X = B^A\) 的条件,因此 Burnside 引理在 \(X \subseteq B^A\) 时仍然成立。

Burnside 引理本质上是更换了枚举量,从而方便计数。

Pólya 定理

前置条件与 Burnside 引理相同,规定 \(X = B^A\) ,则有 Pólya 定理:

\[|X / G| = \frac{1}{|G|} \sum_{g \in G} |B|^{c(g)} \]

其中 \(c(g)\) 表示置换 \(g\) 拆出的不相交轮换数量。

证明:在 Burnside 引理中,\(g(x) = x\) 的充要条件是 \(x\)\(g\) 中每个轮换内的元素都映射到了 \(B\) 中的同一个元素,因此 \(|X^g| = |B|^{c(g)}\)

不难发现在 \(A \to B\) 映射有限制(\(X \neq B^A\))时不能用 Pólya 定理,只能用 Burnside 引理。

应用

P4980 【模板】Pólya 定理

给定一个 \(n\) 个点的环,有 \(n\) 种颜色,给每个点染色,求本质不同的染色方案数 \(\bmod (10^9 + 7)\)

若两个方案可以通过旋转变得相同,则它们本质相同。

\(n \leq 10^9\)

考虑 Polya 定理,对于所有旋转的置换 \(G\) ,设 \(g \in G\) 表示旋转 \(k\) 步,则 \(c(g) = \gcd(k, n)\) ,因此答案即为 \(\frac{1}{n} \sum_{i = 1}^{n - 1} n^{\gcd(i, n)}\) ,然后就是推式子:

\[\begin{aligned} \frac{1}{n} \sum_{i = 0}^{n - 1} n^{\gcd(i, n)} &= \frac{1}{n} \sum_{i = 1}^n n^{\gcd(i, n)} \\ &= \frac{1}{n} \sum_{d \mid n} n^d \sum_{k = 1}^{\frac{n}{d}} [\gcd(k, \frac{n}{d}) = 1] \\ &= \frac{1}{n} \sum_{d \mid n} n^d \varphi(\frac{n}{d}) \\ \end{aligned} \]

暴力求欧拉函数即可,时间复杂度 \(O(n^{\frac{3}{4}})\)

#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;

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 euler_phi(int n) {
    int phi = n;

    for (int i = 2; i * i <= n; ++i)
        if (!(n % i)) {
            phi = phi / i * (i - 1);

            while (!(n % i))
                n /= i;
        }

    if (n > 1)
        phi = phi / n * (n - 1);

    return phi;
}

signed main() {
    int T;
    scanf("%d", &T);

    while (T--) {
        int n, ans = 0;
        scanf("%d", &n);

        for (int i = 1; i * i <= n; ++i)
            if (!(n % i)) {
                ans = add(ans, 1ll * mi(n, i) * euler_phi(n / i) % Mod);

                if (n / i != i)
                    ans = add(ans, 1ll * mi(n, n / i) * euler_phi(i) % Mod);
            }

        printf("%d\n", 1ll * ans * mi(n, Mod - 2) % Mod);
    }

    return 0;
}

P1446 [HNOI2008] Cards

\(S_r + S_b + S_g\) 张牌,需要给每张牌染色为 \(S_r\) 张红色、\(S_b\) 张蓝色、\(S_g\) 张绿色。

给出 \(m\) 种不同的洗牌法,保证:

  • 每种洗牌法都是一个 \(1 \sim n\) 的排列。
  • 任意多次洗牌都可用这 \(m\) 种洗牌法中的一种代替。
  • 对于每种洗牌法,都存在一种洗牌法使得能回到原状态。

两种染色方法相同当且仅当其中一种可以通过任意次洗牌成另一种,求不同染色方案数 \(\bmod P\)

\(S_r, S_b, S_g \leq 20\)\(m \leq 60\)\(P\) 为质数

求有多少种不同的染色方案。

不难发现给出的洗牌法加上单位置换后构成置换群,而染色方案是有数量限制的,因此考虑 Burnside 引理。

考虑每个置换下轮换,其内部的位置的染色必须相同。考虑决策每个轮换染那种颜色,可以通过一个类似背包的过程实现。

时间复杂度 \(O(m n S_r S_b S_g)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 6e1 + 7;

int f[N][N][N], tr[N];
bool vis[N];

int r, b, g, n, m, Mod;

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 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 solve() {
    memset(vis, false, sizeof(vis));
    vector<int> vec;
    
    for (int i = 1; i <= n; ++i)
        if (!vis[i]) {
            vec.emplace_back(0);
            
            for (int x = i; !vis[x]; x = tr[x])
                ++vec.back(), vis[x] = true;
        }
        
    memset(f, 0, sizeof(f));
    f[0][0][0] = 1;
    
    for (int it : vec)
        for (int i = r; ~i; --i)
            for (int j = b; ~j; --j)
                for (int k = g; ~k; --k) {
                    if (i >= it)
                        f[i][j][k] = add(f[i][j][k], f[i - it][j][k]);
                    
                    if (j >= it)
                        f[i][j][k] = add(f[i][j][k], f[i][j - it][k]);
                    
                    if (k >= it)
                        f[i][j][k] = add(f[i][j][k], f[i][j][k - it]);
                }
    
    return f[r][b][g];
}

signed main() {
    scanf("%d%d%d%d%d", &r, &b, &g, &m, &Mod);
    n = r + b + g, iota(tr + 1, tr + n + 1, 1);
    int ans = solve();
    bool flag = false;
    
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j)
            scanf("%d", tr + j);
        
        if (is_sorted(tr + 1, tr + n + 1))
            flag = true;
        else
            ans = add(ans, solve());
    }
    
    printf("%d", 1ll * ans * mi(m - flag + 1, Mod - 2) % Mod);
    return 0;
}

P4128 [SHOI2006] 有色图

有一张 \(n\) 个点的完全图,边有 \(m\) 种颜色,求本质不同的图的数量 \(\bmod p\)

两张图本质相同,当且仅当存在排列 \(p_{1 \sim n}\) ,满足对于第一张图中的任意边 \((u, v)\) 颜色与第二张图中 \((p_u, p_v)\) 边的颜色相同。

类似的题(\(n \leq 60\)\(m = 2\)):P4727 [HNOI2009] 图的同构计数

\(n \leq 53\)

首先发现本题的置换对象是点,但是映射关系对象是边,无法直接使用 Pólya 定理,考虑将点置换转换为边置换的关系。

考虑一个点置换 \(\begin{pmatrix} 1 & 2 & \cdots & n \\ p_1 & p_2 & \cdots & p_n \end{pmatrix}\) ,定义其对应的边置换为 \(\begin{pmatrix} (1, 2) & (2, 3) & \cdots & (n - 1, n) \\ (p_1, p_2) & (p_1, p_3) & \cdots & (p_{n - 1}, p_n) \end{pmatrix}\)

对于点置换的一个轮换 \((a_1, a_2, \cdots, a_k)\) ,在边置换中一定存在轮换 \(((a_1, a_2), (a_2, a_3), \cdots, (a_{k - 1}, a_k), (a_k, a_1))\) ,因此对于边置换中首尾相连的轮换,其可以对应到一个点轮换。

因为定义的边置换与点置换构成双射,因此边置换的乘法与点置换的乘法等价,因此定义边置换满足群的性质,群的阶为 \(n!\)

下面考虑算边置换的轮换数,对于一个点轮换,其会对应若干个边轮换,而每个边轮换中的边的长度相同。这里边的长度定义为两点在点轮换环上的距离,由于是无向边,因此定义长度为较短的弧长。

分两种情况讨论边的轮换:

  • 两端点在同一点轮换内:则长度相同的边对应同一等价类,记轮换长度为 \(l\) ,则共有 \(\lfloor \frac{l}{2} \rfloor\) 个轮换。
  • 两端点不在同一点轮换内:那么会有边循环 \(((a_1, b_1), (a_2, b_2), \cdots)\) ,设两个点轮换的循环节为 \(l_a, l_b\) ,则每个轮换的长度均为 \(\mathrm{lcm}(a, b)\) ,而共有 \(l_a \times l_b\) 条边,因此边轮换的数量为 \(\frac{l_a \times l_b}{\mathrm{lcm}(l_a, l_b)} = \gcd(l_a, l_b)\)

综上,边置换的轮换数为:

\[\sum_{i = 1}^k \lfloor \frac{l_i}{2} \rfloor + \sum_{i = 1}^{k - 1} \sum_{j = i + 1}^{k} \gcd(l_i, l_j) \]

暴力枚举点的轮换大小 \(b_{1 \sim m}\) 满足 \(b_1 \leq b_2 \leq \cdots \leq b_m\)\(\sum_{i = 1}^m b_i = n\)(实际上就是 \(n\) 的拆分):

  • 先把每个数分配到轮换中,方案数为 \(\binom{n}{b_1, b_2, \cdots, b_n} , =\frac{n!}{\prod_{i = 1}^m b_i!}\)
  • 再考虑轮换内部的顺序,即圆排列的方案数 \(\prod_{i = 1}^m (b_i - 1)!\)
  • 但是这样还会算重,因为对于若干个大小相同的轮换,点的分配实际是等价的,因此方案数还要除以 \(\prod cnt_i !\) ,其中 \(cnt_i = \sum_{j = 1}^m [b_j = i]\)

时间复杂度 \(O(\sum_{p \in \mathrm{Partition(n)}} \mathrm{len}(p)^2)\)A296010)。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e1 + 7;

vector<int> num;

int fac[N], inv[N], invfac[N], g[N][N], pw[N * N * N];

int n, m, Mod, ans;

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(int n, int m) {
    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;
    }

    for (int i = 1; i <= n; ++i) {
        g[0][i] = g[i][0] = i;

        for (int j = i; j <= n; ++j)
            g[i][j] = g[j][i] = g[j % i][i];
    }

    pw[0] = 1;

    for (int i = 1; i <= n * n * n; ++i)
        pw[i] = 1ll * pw[i - 1] * m % Mod;
}

void dfs(int x, int s) {
    if (!s) {
        int sum = 0, mul = fac[n];
        
        for (int it : num)
            sum += it / 2, mul = 1ll * mul * inv[it] % Mod;
        
        for (int i = 0; i < num.size(); ++i)
            for (int j = i + 1; j < num.size(); ++j)
                sum += g[num[i]][num[j]];
        
        int cnt = 1;
        
        for (int i = 1; i < num.size(); ++i) {
            if (num[i] != num[i - 1])
                mul = 1ll * mul * invfac[cnt] % Mod, cnt = 1;
            else
                ++cnt;
        }
        
        ans = add(ans, 1ll * mul * invfac[cnt] % Mod * pw[sum] % Mod);
        return;
    }
    
    for (int i = x; i <= s; ++i)
        num.emplace_back(i), dfs(i, s - i), num.pop_back();
}

signed main() {
    scanf("%d%d%d", &n, &m, &Mod);
    prework(n, m), dfs(1, n);
    printf("%d", 1ll * ans * invfac[n] % Mod);
    return 0;
}

BZOJ1547 周末晚会

\(n\) 个球围成一圈,求满足如下条件的染色方案数 \(\bmod (10^8 + 7)\)

  • 每个球的颜色为黑色或白色。
  • 黑球连续段长度 \(\leq k\)

其中循环同构会被认为是同一种方案。

\(n, k \leq 2 \times 10^3\)

由于染色有限制,考虑 Burnside 引理。

先考虑链上的DP,设 \(f_{i, j}\) 表示考虑前 \(i\) 个球、末尾有 \(j\) 个黑球的方案数,转移时注意第二维不超过 \(k\) 即可。

下面考虑循环同构,若旋转 \(d\) 步,则每段的长度为 \(l = \gcd(d, n)\) ,要求每段都相同(不动点)。

枚举首尾相接的黑球数量,答案即为 \(\sum_{i = 0}^{\min(k, l - 1)} (i + 1) \times f_{l - i, 0}\) 。注意这里不能算全是黑球的情况,该情况需要特殊判断 \(k \geq n\)

时间复杂度 \(O(nk)\)

#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e8 + 7;
const int N = 2e3 + 7;

int f[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;
}

inline int solve(int d) {
    int res = (n == k);

    for (int i = 0; i <= min(k, d - 1); ++i)
        res = add(res, 1ll * (i + 1) * f[d - i][0] % Mod);
    
    return res;
}

signed main() {
    int T;
    scanf("%d", &T);
    
    while (T--) {
        scanf("%d%d", &n, &k), k = min(n, k);
        memset(f, 0, sizeof(f)), f[1][0] = 1;
        
        for (int i = 2; i <= n; ++i) {
            for (int j = 0; j + 1 <= min(k, i); ++j)
                f[i][j + 1] = f[i - 1][j], f[i][0] = add(f[i][0], f[i - 1][j]);
            
            if (k <= i)
                f[i][0] = add(f[i][0], f[i - 1][k]);
        }
        
        int ans = 0;
        
        for (int i = 1; i <= n; ++i)
            ans = add(ans, solve(__gcd(i, n)));
        
        printf("%d\n", 1ll * ans * mi(n, Mod - 2) % Mod);
    }
    
    return 0;
}

P4916 [MtOI2018] 魔力环

\(n\) 个球围成一圈,求满足如下条件的染色方案数 \(\bmod 998244353\)

  • 恰有 \(m\) 个黑球和 \(n - m\) 个白球。
  • 黑球连续段长度 \(\leq k\)

其中循环同构会被认为是同一种方案。

\(n, k \leq 10^5\)

由于染色有限制,考虑 Burnside 引理,设 \(f(n, m)\) 表示 \(n\) 个点的环、恰有 \(m\) 个黑球连续段长度 \(\leq k\) 的方案数(不考虑循环同构),答案即为:

\[\frac{1}{n} \sum_{d = 1}^n [\frac{n}{\gcd(d, n)} \mid m] f(\gcd(d, n), \frac{m}{\frac{n}{\gcd(d, n)}}) \]

欧拉反演得到:

\[\frac{1}{n} \sum_{i \mid n} [\frac{n}{i} \mid m] \varphi(\frac{n}{i}) f(i, \frac{m}{\frac{n}{i}}) \]

下面考虑算 \(f(n, m)\) ,先特判全是黑色或全是白色的情况。枚举前后段黑球的数量,答案即为:

\[\sum_{i = 0}^{\min(n, m, k)} (i + 1) g(n - m - 1, m - i) \]

其中 \(g(n, m)\) 表示将 \(m\) 个球塞入 \(n\) 个空隙中,每个空隙 \(\leq k\) 个的方案数,则不难 \(O(n)\) 容斥计算答案。

\(g = \gcd(n, m)\) ,时间复杂度 \(O(n + \sigma(g)) = O(n + g \log \log g)\)

#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 1e5 + 7;

int fac[N], inv[N], invfac[N], pri[N], phi[N];
bool isp[N];

int n, m, 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 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;
    }

    memset(isp, true, sizeof(isp));
    isp[1] = false, phi[1] = 1;

    for (int i = 2; i <= n; ++i) {
        if (isp[i])
            pri[++pcnt] = i, phi[i] = i - 1;

        for (int j = 1; j <= pcnt && i * pri[j] <= n; ++j) {
            isp[i * pri[j]] = false;

            if (i % pri[j])
                phi[i * pri[j]] = phi[i] * phi[pri[j]];
            else {
                phi[i * pri[j]] = phi[i] * pri[j];
                break;
            }
        }
    }
}

inline int C(int n, int m) {
    return m > n || m < 0 ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}

inline int calc2(int n, int m) {
    int res = 0;

    for (int i = 0; i <= min(n, m / (k + 1)); ++i)
        res = add(res, 1ll * sgn(i) * C(n, i) % Mod * C(m - i * (k + 1) + n - 1, n - 1) % Mod);

    return res;
}

inline int calc1(int n, int m) {
    if (m <= k)
        return C(n, m);

    int res = 0;

    for (int i = 0; i <= min(min(n, m), k); ++i)
        res = add(res, 1ll * (i + 1) * calc2(n - m - 1, m - i) % Mod);

    return res;
}

signed main() {
    scanf("%d%d%d", &n, &m, &k);
    prework(n);

    if (n == m || !m)
        return printf("%d", !m || (k >= n)), 0;

    int ans = 0;

    for (int i = 1; i * i <= n; ++i) {
        if (!(n % i)) {
            if (!(m % (n / i)))
                ans = add(ans, 1ll * phi[n / i] * calc1(i, m / (n / i)) % Mod);

            if (n / i != i && !(m % i))
                ans = add(ans, 1ll * phi[i] * calc1(n / i, m / i) % Mod);
        }
    }

    printf("%d", 1ll * ans * inv[n] % Mod);
    return 0;
}
posted @ 2025-06-02 21:02  wshcl  阅读(47)  评论(0)    收藏  举报