群论杂记
群论杂记
群
群的定义与性质
群的定义:群 \((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]\) 表示 \(G\) 中 \(H\) 不同的陪集数。
证明:\(H\) 的的陪集要么相等、要么不交。
置换群
置换的定义:有限集合到自身的双射。对于不可重集合 \(S = \{ a_1, a_2, \cdots, a_n \}\) 上的置换 \(f\) 可以表示为:
即 \(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)(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^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 引理:
证明:
\[\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 定理:
其中 \(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)}\) ,然后就是推式子:
暴力求欧拉函数即可,时间复杂度 \(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)\) 。
综上,边置换的轮换数为:
暴力枚举点的轮换大小 \(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\) 的方案数(不考虑循环同构),答案即为:
欧拉反演得到:
下面考虑算 \(f(n, m)\) ,先特判全是黑色或全是白色的情况。枚举前后段黑球的数量,答案即为:
其中 \(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;
}