前置知识
群,置换,循环,轨道,不动点。
设 (G) 为有限群,(X) 为一个集合,(x in X),定义 (x) 的轨道为
定义 (X) 的轨道数 (L = |{ G_x | x in X }|),(X^g = {x | x in X, gx = x }) 表示集合 (X) 中的所有不动点。
所以,通俗地来说,轨道数就是本质不同的 xxx 的数量。
定理
Burnside 引理:
然而并不会证明。
例子
洛谷P4980 【模板】Pólya 定理
这里 (G) 中的元素都表示一个旋转了多少的置换。
而如果旋转了 (i) 格,那么有 (gcd(n, i)) 个循环,要是不动点则必须满足循环上每个点颜色相同,所以不动点数目就是 (n^{gcd(n, i)})。
所以,答案就是:
洛谷P4708 画画
将无标号转成有标号,那么 (G) 就是点置换,这样 (L) 就表示本质不同的每个点度数都是偶数的图的个数。
可以发现,如果我们要求不动点数目,那么只和每个置换的循环长度可重集有关,那么可以列出式子:
现在考虑求 (F)。
1. 循环内部连边
对于一个循环 ((a_0a_1cdots a_{k - 1})),若 (a_i) 和 (a_j) 有一条边,那么 (a_{i + 1 mod k}) 和 (a_{j + 1 mod k}) 之间也一定要连边。
当 (k) 为奇数时,(lfloor frac k2 floor) 种合法的连边方案都不会改变任意点度数的奇偶性。
当 (k) 为偶数时,仅有 (a_0) 和 (a_{k / 2}) 连边会导致所有点度数奇偶性发生改变,还有 (k / 2 - 1) 种方案没有影响,可以直接加入答案中。
2. 循环间连边
对于任意两个循环 ((a_0a_1cdots a_{i-1})) 和 ((b_0b_1cdots b_{j - 1})),若 (a_x) 和 (b_y) 有一条边,那么 (a_{x + 1 mod k}) 和 (b_{y + 1 mod k}) 也一定要有连边。
所以说,两个循环中有 (gcd(i, j)) 种连边方式,同时每种方式给每个点带来的度数变化是一样的。
由于一种方式会产生 (operatorname{lcm}(i,j )) 条边,所以每个点 (a_x) 就会连出 (frac {j} {gcd(i, j)}) 条边,(b_y) 同理。
如果这条边只改变了 (a, b) 中一个循环的度数奇偶性,那么就可以当成一个点对自己的作用;如果同时改变了,记录下来;否则可以直接乘到答案中去。
可以发现循环中每个点度数的奇偶性在任意时刻都相同,那么将一个循环看成一个点。
此时问题转化为:给定 (k) 个点和 (e) 条边,每个点有 (p_i) 次机会使自己的点权异或 (1),对于每条边都有机会使边上两个端点的权值异或 (1),问操作完之后每个点权值不变的方案数。
假设图连通(如果不连通就直接分开做),考虑每条边都不会改变 (k) 个点权值和的奇偶性,所以每个点的操作次数之和必须为偶数,所以操作点的方案数就是 (2 ^ {max(sum p_i - 1, 0)})。
接下来考虑原图的一棵生成树,可以发现只要非树边的方案确定,树边的方案可以唯一确定,所以这一部分的方案数就是 (2 ^ {e - k + 1})。
总方案数就是 (2 ^ {max(sum p_i - 1, 0) + e - k + 1})。
#include <cstdio>
const int N(55), Mod(998244353);
inline int upd(const int &x) { return x + (x >> 31 & Mod); }
int fastpow(int x, int y)
{
int ans = 1;
for (; y; y >>= 1, x = 1ll * x * x % Mod)
if (y & 1) ans = 1ll * ans * x % Mod;
return ans;
}
int n, m, a[N], c[N], fac[N], inv[N], g[N][N], fa[N], p[N], ans;
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void calc()
{
int res = 1, tot = -m;
for (int i = 1; i <= a[m]; i++) if (c[i]) res = 1ll * res * inv[c[i]] % Mod;
for (int i = 1; i <= m; i++) res = 1ll * res * inv[a[i]] % Mod * fac[a[i] - 1] % Mod;
for (int i = 1; i <= m; i++) fa[i] = i, p[i] = 0;
for (int i = 1; i <= m; tot += (a[i] - 1) >> 1, i++) if (!(a[i] & 1)) ++p[i];
for (int i = 1; i <= m; i++)
for (int j = i + 1; j <= m; j++)
{
int d = g[a[i]][a[j]], x = (a[j] / d) & 1, y = (a[i] / d) & 1;
if (!x && !y) tot += d; else if (!y) p[i] += d; else if (!x) p[j] += d;
else tot += d, fa[find(i)] = find(j);
}
for (int i = 1; i <= m; i++) if (i != find(i)) p[find(i)] += p[i];
for (int i = 1; i <= m; i++) if (find(i) == i) tot += p[i] ? p[i] : 1;
ans = (ans + 1ll * res * fastpow(2, tot)) % Mod;
}
void dfs(int n, int d)
{
if (n == 0) return calc();
for (int i = d; i <= n; i++) ++c[a[++m] = i], dfs(n - i, i), --m, --c[i];
}
int main()
{
std::scanf("%d", &n), fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[n] = fastpow(fac[n], Mod - 2);
for (int i = n; i; i--) inv[i - 1] = 1ll * inv[i] * i % Mod;
for (int i = 1; i <= n; i++) g[i][0] = g[0][i] = i;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++) g[i][j] = g[j][i] = g[j][i % j];
dfs(n, 1), printf("%d
", ans);
return 0;
}