「学习笔记」Burnside 引理 & Polya 定理
前置知识
群,置换,循环,轨道,不动点。
设 \(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_1\cdots a_{k - 1})\),若 \(a_i\) 和 \(a_j\) 有一条边,那么 \(a_{i + 1 \bmod k}\) 和 \(a_{j + 1 \bmod k}\) 之间也一定要连边。
当 \(k\) 为奇数时,\(\lfloor \frac k2 \rfloor\) 种合法的连边方案都不会改变任意点度数的奇偶性。
当 \(k\) 为偶数时,仅有 \(a_0\) 和 \(a_{k / 2}\) 连边会导致所有点度数奇偶性发生改变,还有 \(k / 2 - 1\) 种方案没有影响,可以直接加入答案中。
2. 循环间连边
对于任意两个循环 \((a_0a_1\cdots a_{i-1})\) 和 \((b_0b_1\cdots b_{j - 1})\),若 \(a_x\) 和 \(b_y\) 有一条边,那么 \(a_{x + 1 \bmod k}\) 和 \(b_{y + 1 \bmod 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\n", ans);
return 0;
}