前言
正如大家所知,我是一个蒟蒻,所以我滚来学群论了QAQ
群论其实是个很厉害的东西,不知道比反演什么的简单到哪里去了。
啊,学渣苦,学渣累。——Friedrich Taylor
什么是群?
首先我们要知道什么是群。
群的定义:给定一个集合G={a,b,c,…}和集合上的二元运算”
∗
”,满足以下四条性质:
1.封闭性:
2.结合律:
∀a,b,c∈G,(a∗b)∗c=a∗(b∗c)
3.单位元:
∃e∈G,∀a∈G,a∗e=e∗a=a
4.逆元:
∀a∈G,∃b∈G,a∗b=b∗a=e,记b=a−1
则称集合G在运算”
∗
”下是一个群,可以简称为G是群。
顺便普及一下关于群的两个定理
1.如果(G,
其中
g∗G={g∗h|h∈G}
2.如果(G,
∗
)是群,H是G的非空子集,且(H,
根据定理2可以判断子集是否为一个子群:
HH=H且H−1=H
等价于H是G的子群。
什么是置换?什么是置换群?
在学习了群的基础概念后,我们还需要一个知识补丁。
n个元素之间的置换
(1a12a23a3......nan)
表示1被1到n中的某个数
a1
取代,直到n被1-n中的某个数
an
取代,且
a1,a2,...,an
互不相同。
置换群的元素是置换,运算是置换连接。
e.g.(13213244)(14233241)=(13213244)(32142341)=(12243341)
貌似我们可以验证置换群的确是群。
具体的简单感性认知如下
1.封闭性:
(1a12a2......nan)(a1b1a2b2......anbn)=(1b12b2......nbn)
2.结合律:
{(1a12a2......nan)(a1b1a2b2......anbn)}(b1c1b2c2......bncn)=(1a12a2......nan){(a1b1a2b2......anbn)(b1c1b2c2......bncn)}
3.单位元:
e=(1122.......nn)
4.逆元:
(1a12a2......nan)−1=(a11a22......ann)
Polya定理
我们都知道大名鼎鼎的Polya定理:
L=1|G|∑si=1mc(gi)
其中
L
表示本质不同的方案数,
首先介绍一下循环的概念:
(a1,a2,...,an)=(a1a2a2a3......an−1anana1)
被称为n阶循环
两个循环
(a1,a2,...an)
与
(b1,b2,...bn)
互不相交,当且仅当
ai≠bj
,其中
i,j=1,2,...,n
.
例如:
(1321324554)=(123)(45)
对于这个置换,其循环节数为2.
e.g.
等边三角形三顶点分别用红绿蓝三种颜色着色,有多少种不同的方案?其中的置换操作为:
(1)沿中心旋转
0∘
,
120∘
,
240∘
(2)沿三条中线翻转
解:易知对于沿中心旋转
0∘
,置换为
(112233)
,循环节数为3
沿中心旋转
120∘
,置换为
(122331)
,循环节数为1
沿中心旋转
240∘
,置换为
(132132)
,循环节数为1
沿三条中线翻转的置换分别为
(112332)
,
(122133)
,
(122133)
,循环节数均为2
根据Polya定理可以知道不同的方案数
L
为
如何证明Polya定理?Burnside!
Burnside引理是群论中的一个结论,可以用来计算等价类的个数,即
L=1|G|∑sj=1D(aj)
其中
D(aj)
表示在置换
aj
下不变的染色方案的个数。
同样我们可以用Burnside引理来解决上述等边三角形的问题
沿中心旋转
0∘
时,所有的
33
种染色方案均不变
沿中心旋转
120∘
或
240∘
时,只有三点颜色相同的染色方案才不变色
分别沿三条中线对折时,分别有
32
种方案不变色
所以L为
16(33+31+31+32+32+32)=10
相信大家都应该发现了:要得到在置换下稳定不动的方案,只需把置换的每个循环节染上相同的颜色即可,所以
D(aj)=mc(aj)
那么我们如何证明Burnside引理呢?
我们先引进一个定理(
x
):
Ek
:设
G
是1,2,…,n的置换群,若
我们通过又一个例题来最终”推导”出Burnside引理
e.g.
对2
∗
2的方块进行黑白染色,能得到多少不同的图像?
其中的置换操作为:
旋转
解:我们很容易根据burnside引理或者是Polya定理亦或是手推都能得到最后的方案一定是6种。那么我们继续无聊但有必要地推导一下:
总方案数:
24
=16种
设方案1为四格均白,则有
|E1|∗|Z1|=1∗4=4
设方案a为只有右上角一个黑格,其馀皆白,则有
|Ea|∗|Za|=4∗1=4
设方案b为右上左下皆为黑格,左上右下均为白格,则有
|Eb|∗|Zb|=2∗2=4
所以定理
x
是可以直观感知的
现在我们推一推Burnside
接下来我们研究每种方案在各个置换下不变的次数之和
设
设
Sij={0,当ai∉Zj,即方案j在置换ai下变动了1,当ai∈Zj,即方案j在置换ai下保持不变
由
Si,1+Si,2+...Si,n=D(ai)
以及
S1,i+S(2,i)+...+S(s,i)=Zk
由此可得
∑nj=1Zj=∑si=1∑nj=1Si,j=∑si=1D(ai)
也就是
∑nj=1Zj=∑si=1D(ai)
或者我们继续假设n个方案中总共有L个等价类
那么
n=E1+E2+...+EL
同时,当
i,j
属于同一等价类时,有
|Zi|=|Zj|
所以可以如是推导:
∑si=1D(ai)=∑nj=1Zj=∑Li=1∑j∈Ei|Zk|=∑Li=1|Ei|∗|Zi|=∑Li=1|G|=L∗|G|
很明显我们求的就是L
所以
L=1|G|∑sj=1D(aj)
,Burnside引理得证
一些简单得如南极冰盖瞬间融化一样水的习题
(1)Cipher(Central Europe 1995)
题面见链接(http://poj.org/problem?id=1026 )
题意:对于一个长度为n的字符串,有一个数组表示第i个字符放到那个位置。输入多个字符串,问这样操作k次后的字符串的形态
题解:暴力肯定是药丸的。
我们可以这样做:对于每个数组,求其循环节,再用总操作数依次模每个字符所在循环节长度后再暴力求解即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline long long read(){
long long i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
void write(long long x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 222
int sze[stan],trans[stan],tmp,n,k,pos,len;
char word[stan],ans[stan];
signed main(){
n=read();
while(n!=0){
memset(sze,0,sizeof(sze));
for(int i=1;i<=n;++i)
trans[i]=read();
for(int i=1;i<=n;++i){
tmp=i;
while(!sze[i]||tmp!=i){
++sze[i];
tmp=trans[tmp];
}
}
k=read();
while(k!=0){
memset(word,0,sizeof(word));
memset(ans,0,sizeof(ans));
gets(word+1);
len=0;
for(int i=1;i<=n;++i){
if(word[i]==0) len=1;
tmp=k%sze[i],pos=i;
while(tmp--)
pos=trans[pos];
if(len) ans[pos]=' ';
else ans[pos]=word[i];
}
ans[n+1]=0;
puts(ans+1);
k=read();
}
puts("");
n=read();
}
return 0;
}
(2)cow sorting(USACO 2007 February Gold)
题面依旧见链接(http://poj.org/problem?id=3270)
题意:给你n个数,按升序排序,可以交换任意两个数且每次交换的代价是这两个数的和,求最小代价。
题解:考虑一种比较优秀的方法:求出这个序列的循环节,那么对于这个循环节,设其为
a1,a2,...ax
,若数列只有该循环节,最小代价为
a1∗(x−1)+(a2+a3+...+ax)
①
但还有一种情况:如果把
a1
换做整个数列最小的数
amin
后再进行上述操作,则花费为
2∗(a1+amin)+amin∗(x−1)+(a2+a3+...+ax)
②
对于每个循环节,取①、②的最小值即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline long long read(){
long long i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
void write(long long x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
#define stan 111111
int m,n,a[stan],pos[stan],inner[stan],mini;
long long ans;
signed main(){
while(scanf("%d",&n)!=EOF){
memset(inner,0,sizeof(inner));
mini=999999999;
for(int i=0;i<n;++i){
a[i]=read();
mini=min(a[i],mini);
pos[a[i]]=i;
}
sort(a,a+n);
for(int i=0;i<n;++i){
if(i==pos[a[i]]||inner[a[i]]) continue;
int temp_pos=i,sze=0;
while(!inner[a[temp_pos]]){
inner[a[temp_pos]]=true;
temp_pos=pos[a[temp_pos]];
++sze;
if(!inner[a[temp_pos]]) ans+=a[temp_pos];
}
ans+=min(a[i]*(sze-1),(a[0]+a[i])*2+a[0]*(sze-1));
}
write(ans);puts(" ");
}
return 0;
}
(3)Let it Bead(Ulm Local 2000)
题面依然见链接(http://poj.org/problem?id=2409)
题意:一个有n个元素的环,有m种颜色,求不同染色方案数
其中的置换操作为:顺时针旋转
1,2,...,n−1
个元素
题解:此处用到一个结论:第i个置换的循环节个数为
gcd(n,i)
然后就是愉快的暴力套Polya
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline long long read(){
long long i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
void write(long long x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
int m,n;
long long ans;
long long gcd(long long a,long long b){
if(b) return gcd(b,a%b);
else return a;
}
long long ksm(long long a,long long b){
long long ret=1;
while(b){
if(b&1) ret*=a;
a*=a;
b>>=1;
}
return ret;
}
signed main(){
m=read();n=read();
while(n!=0&&m!=0){
ans=0;
if(n==0||m==0){
puts("0");
}else{
for(int i=1;i<=n;++i)
ans+=ksm(m,gcd(n,i));
if(n&1)
ans+=n*ksm(m,(n+1)/2);
else
ans+=(n>>1)*(ksm(m,n/2)+ksm(m,n/2+1));
write(ans/(2*n));
puts(" ");
}
m=read();n=read();
}
return 0;
}
(4)Magic Bracelet(POJ2888)
题面见链接(http://poj.org/problem?id=2888)
题意:n个元素m种颜色,某些颜色不能匹配,求染色方案数对9973取模。其中的置换操作为顺时针旋转
1,2,...n−1
个元素。
题解:因为有限制所以只能用Burnside引理。
但是因为n达到了
109
,显然不能暴力枚举
观察发现
∑Ni=1e[gcd(i,N)=k]=ϕ(N/k)
所以我们只需要枚举
1
到
同时因为n过大我们也需要矩阵快速幂优化
注意这道题卡模运算常数
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#define mod 9973
using namespace std;
inline int read(){
int i=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch);ch=getchar())
if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar())
i=(i<<3)+(i<<1)+(ch^48);
return i*f;
}
int buf[1024];
void write(int x){
if(!x){putchar('0');return ;}
if(x<0){putchar('-');x=-x;}
while(x){buf[++buf[0]]=x%10,x/=10;}
while(buf[0]) putchar(buf[buf[0]--]+48);
return ;
}
int n,m,k,T,a,b;
struct matrix{
int mat[11][11];
inline void I()
{
memset(mat,0,sizeof(mat));
for(int i=1;i<=m;i++)mat[i][i]=1;
}
inline matrix operator * (const matrix &b)
{
matrix c;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
{
int sum=0;
for(int k=1;k<=m;k++)
sum+=mat[i][k]*b.mat[k][j];
c.mat[i][j]=sum%mod;
}
return c;
}
inline matrix operator ^ (int x)
{
matrix res,tmp=*this;
res.I();
for(;x>0;x>>=1,tmp=tmp*tmp)
if(x&1)res=res*tmp;
return res;
}
}basic;
int getphi(int x){
int ret=x;
for(int i=2;i*i<=x;++i)
if(x%i==0){
ret-=ret/i;
while(x%i==0) x/=i;
}
if(x>1) ret-=ret/x;
return ret%mod;
}
int calc(matrix x){
int ret=0;
for(int i=1;i<=m;++i)
ret+=x.mat[i][i];
return ret%mod;
}
int inv(int n){
n%=mod;
int b=mod-2,ret=1;
while(b){
if(b&1) ret=(ret*n)%mod;
n=(n*n)%mod;
b>>=1;
}
return ret;
}
int getans(int n){
int ret=0;
for(int i=1;i*i<=n;++i)
if(n%i==0){
ret=(ret+calc(basic^i)*getphi(n/i))%mod;
if(i*i<n) ret=(ret+calc(basic^(n/i))*getphi(i))%mod;
}
return ret*inv(n)%mod;
}
signed main(){
T=read();
while(T--){
n=read();m=read();k=read();
for(int i=1;i<=m;++i)
for(int j=1;j<=m;++j)
basic.mat[i][j]=1;
for(int i=1;i<=k;++i){
a=read();b=read();
basic.mat[a][b]=basic.mat[b][a]=0;
}
write(getans(n));
puts("");
}
return 0;
}
小结
啊,终于水完了。
群论还是很有趣的
这篇文章(我们姑且这么称呼这一大摞字)主要涉及的是关于群论中Polya定理的证明、理解以及群论的常识。
在信息学竞赛中,群论的主要运用是Polya定理以及Burnside引理进行组合计数。而作为研究贪心算法的数学基础的拟阵在本文中毫无提及。
感谢现代群论的创始者迦罗瓦先生(Évariste Galois,1811-1832)。
本文的脉络思路主要参考了林厚从先生《信息学奥赛之数学一本通》(2016年版)关于群论一章的内容。该书的编者之一,闫书弈先生于NOI2017以627的高分夺rank1,%%%
这里甩一个课件
哈工程沈晶老师的PPT:(https://wenku.baidu.com/view/2c7a737b02768e9951e7387a.html?from=search)