学习笔记:从 0 到 Pólya 原理
a_small_OIer · · 算法·理论
前言
军训的时候见缝插针读完了符文杰老师的《Pólya 原理及其应用》和陈瑜希老师的《Pólya 计数法的应用》,遂作此文。
本文将会是 Self-contained(自包含)的,将会从一些群论的基本概念开始写起,直到 Pólya 原理,并通过一些有关的例题讲解 Pólya 原理在 OI 题目的应用。
由于笔者自身能力的限制,本文可能存在一些纰漏和错误的地方,同时例题的难度可能也不会很大。希望您能谅解,如果发现问题欢迎各位大佬指出。
感谢审核老师的认真审核。
引言
Pólya 计数定理(Pólya Enumeration Theorem)是组合数学中计算染色问题“本质不同”的方案数的一个非常高效简便的工具,由匈牙利裔美国数学家、数学教育家 George Pólya(1887-1985) 在 1937 年提出。
理解 Pólya 计数定理需要一些群论的知识,下面将会从群的定义开始介绍。
从 0 到 Pólya 原理
群
群(Group)是数学中的一种代数结构,由一个集合
- 封闭性:对于
G 中的任意元素a, b ,a \times b \in G ; - 结合律:对于
G 中的任意元素a, b, c ,(a \times b) \times c = a \times (b \times c) ; - 单位元:存在一个元素
e \in G ,使得对于G 中的任意元素a ,e \times a = a \times e = a ; - 逆元:对于
G 中的任意元素a ,存在一个元素a^{-1} \in G ,使得a \times a^{-1} = a^{-1} \times a = e 。
如果一个集合
和字符串一样,我们使用
置换和置换群
置换(Permutation)是集合的一种操作,记作
例如,对于集合
置换可以写作类似矩阵的形式:
置换群(Permutation Group)是若干置换在复合运算下构成的群,其基本运算为置换的复合,例如:
轨道稳定子定理
在正式介绍 Burnside 引理之前,我们需要先理解群在集合上的作用,以及与之密切相关的两个核心概念:轨道(Orbit)和稳定子(Stabilizer)。
群作用
设
满足以下两条性质:
- 恒等作用:对任意
x \in X ,e \cdot x = x ,其中e 是群G 的单位元; - 相容性:对任意
g_1, g_2 \in G 和任意x \in X ,(g_1 g_2) \cdot x = g_1 \cdot (g_2 \cdot x) ,
则称群
在 Pólya 计数的语境中,
X 通常代表所有染色方案的集合,而G 是作用于这些方案上的置换群(例如旋转、翻转等)。群作用描述的就是:给定一个置换,它能将一种染色方案变成另一种“看起来不同但实际等价”的方案。
轨道
设群
这个集合称为
直观理解:如果两种染色方案处于同一个轨道中,那么它们可以通过某个群操作互相转化,因此它们是“本质相同”的。所以,我们想要计数的“本质不同”的方案数,实际上就是轨道的个数。
稳定子
对于
这个集合称为
不难验证,
轨道稳定子定理
定理(Orbit-Stabilizer Theorem):设有限群
这个定理告诉我们:轨道的大小乘以稳定子的大小,恰好等于整个群的大小。
证明:
固定一个元素
由轨道的定义,这个映射显然是满射。我们想知道有多少个不同的群元素映射到轨道中的同一个元素。
取轨道中的任意一个元素
我们发现
那么所有满足
而一个左陪集的大小正好等于
于是
故得证。
Burnside 引理
定义
设有限群
其中
证明:
考虑集合
一方面,按群元素
另一方面,按元素
将
综上可得
即
证毕。
循环和 Pólya 原理
我们发现 Burnside 引理中
那么这就需要进一步介绍 Pólya 原理了,在此之前,我认为还需要介绍循环的概念。
记
读者一定可以看出这个置换的循环性质,如上述的置换我们称之为一个
我们称两个循环
任何一个置换都是若干个不相交的循环的积,例如
我们称这个置换的循环节数为
记
所以有:
那么我们就可以将
这就是 Pólya 原理。
一个例子
这里引用符文杰老师在《Pólya 原理及其应用》使用的例子来帮助理解 Pólya 原理:
给定一个
:::align{center}
| 符文杰老师在《Pólya 原理及其应用》的附图 |
|---|
通过枚举我们可以知道有且只有上图
通过 Burnside 引理求解
通过观察我们可以发现本例中共有
通过 Pólya 原理求解
我们将
由此可得各置换的循环节数为
希望这个例子能帮助读者更好地理解 Burnside 引理和 Pólya 原理。
Pólya 原理在 OI 中的应用(一些例题)
正如作者在前言中所说,受限于作者的能力限制,本节选取的题目难度可能较小,笔者希望通过一些比较经典的题目帮助读者来更好的理解 Pólya 原理,选取的例题考察的主要是 Pólya 原理的基本应用,而不涉及更高难度的优化,在此笔者表示歉意。
在 OI 竞赛中,单独考察 Burnside 引理和 Pólya 原理的题目较少,一般要进行一些优化,希望读者可以通过下文获得一些启发。
{\color{#9D3DCF} \text{P2561}} [AHOI2002] 黑白瓷砖
题意:求一个由
分析:
之所以将本题放在模板前面是因为笔者认为本题没有任何转换,难度要小于模板。
我们记瓷砖数为
由题可知本题共包含
- 恒等变换:循环节数显然为
T 。 -
- $T$ 是 $3$ 的倍数:没有中心的那块瓷砖,循环节数 $c_1 = \frac{T}{3}$。 - $T$ 不是 $3$ 的倍数:此时有中心的那块瓷砖,显然循环节数 $c_1 = \frac{T+2}{3}$。 - 翻转:三个翻转实质一样,这里统一讨论。翻转轴上的瓷砖一定不动,数量为
f = \left \lceil \frac{n}{2} \right \rceil ,其余T-f 瓷砖两两互换,总循环节数为c_2 = \frac{T+f}{2} 。
带入 Pólya 公式即可,答案为
实现:注意到本题的
这里笔者选择了使用 Python 实现,因而忽略了高精度乘法的实现,因为笔者认为高精度运算与本文关系不大,因此不在此赘述。
n = int(input())
T = n * (n + 1) // 2
if T % 3 == 0:
c1 = T // 3
else:
c1 = (T + 2) // 3
F = (n + 1) // 2
c2 = (T + F) // 2
ans = ((1 << T) + 2 * (1 << c1) + 3 * (1 << c2)) // 6
print(ans)
#其实就是笔者太懒了,还找理由。
#强烈谴责!
{\color{#9D3DCF} \text{P4980}} 【模板】Pólya 定理
题意:给定一个
分析:
本题的置换群为
由 Pólya 定理,本质不同的染色方案数为
直接枚举
令
我们知道当
实现:
#include<bits/stdc++.h>
#define P 1000000007
using namespace std;
typedef long long LL;
//十年OI一场空,不开long long见祖宗
LL qp(LL a , LL b){
LL res = 1;
while(b){
if(b & 1)
res = res * a % P;
a = a * a % P;
b >>= 1;
}
return res;
}
LL n , sum;
int T , cnt;
LL p[64] , e[64];
inline void fac(LL n){
LL x = n;
cnt = 0;
for(LL p_ = 2 ; p_ * p_ <= x ; ++p_){
if(x % p_ == 0){
int e_ = 0;
while(x % p_ == 0)
x /= p_ , e_ += 1;
p[++cnt] = p_,
e[cnt] = e_;
}
}
if(x > 1)
p[++cnt] = x , e[cnt] = 1;
}
inline void dfs(int idx , LL d , LL phi){
if(idx == cnt + 1){
sum = (sum + (phi % P) * qp(n , n / d) % P) % P;
return;
}
LL p_ = p[idx];
int e_ = e[idx];
dfs(idx + 1 , d , phi);
LL d_ = 1; //p ^ i
LL phi_ = 1;
for(int i = 1 ; i <= e_ ; ++i){
d_ *= p_;
if(i == 1)
phi_ *= (p_ - 1);
else
phi_ *= p_;
dfs(idx + 1 , d * d_ , phi * phi_);
}
}
int main(){
//freopen(".in" , "r" , stdin);
//freopen(".out" , "w" , stdout);
scanf("%d" , &T);
while(T--){
scanf("%lld" , &n);
fac(n);
sum = 0;
dfs(1 , 1 , 1);
LL ans = sum % P * qp(n % P , P - 2) % P;
printf("%lld\n" , ans);
}
return 0;
}
//我常常追忆过去
//我该在哪停留,我问我自己。
{\color{#0E1D69} \text{P4128}} [SHOI2006] 有色图
题意:给定
分析:
本题的置换群为所有
设一个顶点置换
这里与前面的题目有一点不同:本题染色的对象是边,而
我们发现对于一个长度为
设两个端点之间较小的环上距离为
因此,一个长度为
同理,对于两个长度分别为
经过
因此最小的
于是这
个边循环。
因此,对于循环长度序列
设长度为
于是,本质不同的染色方案数为
式中
对于分母的处理,我们注意到题目保证
实现:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
//十年OI一场空,不开long long见祖宗
LL qp(LL a , LL b , LL mod){
LL res = 1;
while(b){
if(b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int n;
LL m , p;
LL ans;
int a[60] , gcd_[60][60];
LL inv[60] , fac[60];
inline void dfs(int x , int x_ , LL cnt , LL inv_){
if(x_ == 0){
ans = (ans + qp(m , cnt , p) * inv_) % p;
return;
}
if(x == n + 1)
return;
int k = x_ / x;
for(int i = 0 ; i <= k ; ++i){
a[x] = i;
LL cnt_ = cnt;
if(i > 0){
cnt_ = cnt_ + i * (x / 2) + 1LL * i * (i - 1) / 2 * x;
for(int j = 1 ; j < x ; ++j)
cnt_ += 1LL * a[j] * i * gcd_[j][x];
}
LL _inv_ = inv_;
if(i > 0){
_inv_ = _inv_ * qp(inv[x] , i , p) % p;
_inv_ = _inv_ * fac[i] % p;
}
dfs(x + 1 , x_ - i * x , cnt_ , _inv_);
}
}
int main(){
//freopen(".in" , "r" , stdin);
//freopen(".out" , "w" , stdout);
scanf("%d%lld%lld" , &n , &m , &p);
m %= p;
for(int i = 1 ; i <= n ; ++i)
inv[i] = qp(i , p - 2 , p);
LL tmp[60];
tmp[0] = 1;
for(int i = 1 ; i <= n ; ++i)
tmp[i] = tmp[i - 1] * i % p;
fac[n] = qp(tmp[n] , p - 2 , p);
for(int i = n ; i >= 1 ; --i)
fac[i - 1] = fac[i] * i % p;
for(int i = 1 ; i <= n ; ++i)
for(int j = 1 ; j <= n ; ++j)
gcd_[i][j] = __gcd(i , j);
ans = 0;
dfs(1 , n , 0 , 1);
printf("%lld\n" , ans);
return 0;
}
//星图铺就的,未必是归途。
//但有人循着它,便不算迷路。
//所以色图在哪里。
总结
本文从群论的一些基本概念出发,介绍了 Pólya 定理和其在 OI 比赛中的简单应用,受笔者个人能力所限,本文的例题难度有限,希望读者谅解。读者也可以自行寻找更多例题进行练习,多思考 Pólya 定理的本质和相关优化,相信读者会有自己更多的感悟和理解。
最后,由衷地感谢您的阅读。
参考文献
[1] Pólya 定理
[2] 符文杰《Pólya 原理及其应用》
[3] 陈瑜希《Pólya 计数法的应用》
AI 使用说明:本文部分章节曾使用 DeepSeek 进行润色和检查,保证笔者贡献严格大于 AI。