群论学习笔记
本文有着大量的感性理解,或没有证明的性质。鄙人尚菜,还请各位看官多多包涵。
一、置换
实际上可以理解为对集合的每个元素一个新的标号,满足这个标号集合与原集合一一对应。
如集合 \(X=\{x_1,x_2,\dots,x_n\}\),他的置换就可以表示为:\(\sigma=(_{x_{p_1}\ \ \ x_{p_2}\ \ \ \dots\ \ \ x_{p_n}}^{\ x_1\ \ \ \ \ x_2\ \ \ \dots\ \ \ \ x_n})\)。
做题时通常满足 \(x_i=i\),我们就可以表示为:\(\sigma=x_{p_1}x_{p_2}\dots x_{p_n}\)。
明显会出现循环节,比如 \(\sigma=265431\),此时 \(x_1,x_2,x_6\) 就是一个循环。根据这个特性,我们还可以将置换表示为:\(\sigma=(126)(35)(4)\),单独的循环节可以不写,即可以省略为:\(\sigma=(126)(35)\)。
考虑可以将交换转化为一个循环节内部的交换,设循环内 \(\sum w_i\) 为 \(sum\),\(\min w_i\) 为 \(min\),循环节的大小为 \(num\),则容易想到一种 \(sum+(num-2)\times min\) 的方案,即从最小值开始交换。
但考虑假如我们先减小循环节最小值,即将全局最小值 \(minn\) 与 \(min\) 交换,再进行上述操作,最后再把 \(min\) 换回来,这样就可以达到 \(sum+min+(num+1)\times minn\) 的代价,两者取 \(\min\) 即可。
时间复杂度 \(O(n)\),其本质等价于置换的循环节式写法。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,ans,minn=2e9,fl[N];
int yc[N],a[N],b[N],w[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i],ans+=w[i];
minn=min(minn,w[i]);
}for(int i=1;i<=n;i++)
cin>>a[i],yc[a[i]]=i;
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++){
if(fl[i]) continue;
int mn=2e9,nm=1,nw=i;
while(!fl[nw]){
mn=min(mn,w[a[nw]]),nm++;
fl[nw]=1,nw=yc[b[nw]];
}ans+=min((nm-3)*mn,nm*minn+mn);
}cout<<ans;
return 0;
}
置换的运算就是一个置换叠加上另一个置换,如:
有这样一个性质:
长度为奇数的循环节平方时不变,长度为偶数的循环节平方时分成长度相等的两段循环节。
手模容易,不再过多赘述。
二、群
群的定义如下:
给出一个集合 \(G=\{a,b,c,\dots\}\) 和集合 \(G\) 上的二元运算 \(*\),并满足:
- \(\forall a,b\in G,\exists c\in G,a*b=c\)。
- \(\forall a,b,c\in G,(a*b)*c=a*(b*c)\)。
- \(\exists e\in G,\forall a\in G,a*e=e*a=a\)。
- \(\forall a\in G,\exists b\in G,a*b=b*a=e,\) 记 \(b=a^{-1}\)。
那么称 \((G,*)\) 为群。
摘自信息学奥赛之数学一本通(\(C++\) 版),有改动。
群的运算如下:
对于 \(g\in G\),\(G\) 的子集 \(H,K\),定义 \(g*H=\{gh|h\in H\}\),简写为 \(gH\);定义 \(H*g=\{hg|h\in H\}\),简写为 \(Hg\);定义 \(H*K=\{hk|h\in H,k\in K\}\),简写为 \(HK\);定义 \(H^{-1}=\{h^{-1}|h\in H\}\)。
摘自信息学奥赛之数学一本通(\(C++\) 版),有改动。
群的部分定理如下:
定理 \(1\):若 \((G,*)\) 为群,则 \(\forall g\in G,gG=Gg=G\)。
定理 \(2\):若 \((G,*)\) 为群,\(H\) 为 \(G\) 的非空子集,并且 \((H,*)\) 为群,则 \(H\) 为 \(G\) 的子群。
定理 \(2\) 扩展:\(HH=H\) 或 \(H^{-1}=H\) 等价于 \(H\) 为 \(G\) 的子群。
摘自信息学奥赛之数学一本通(\(C++\) 版),有改动。
三、置换群
置换群的元素就是置换,运算就是置换的运算,容易证明置换群满足群的四个条件。
四、\(Burnside\) 引理
用 \(D(a_j)\) 表示在置换 \(a_j\) 中位置没有发生改变的元素的个数,\(L\) 表示本质不同的方案数,则有:
\[L=\frac{1}{|G|}\sum\limits_{j=1}^{s}D(a_j) \]
考虑构造置换群。
首先他给你了 \(m\) 个置换,他们满足结合律,但是没有单位元。置换群中的单位元容易想到 \(\sigma=123\dots\),插入他不会对答案产生影响。所以我们构造了一个大小为 \(m+1\) 的置换群。
容易发现一个置换中的一个循环节中的所有位置都必须得是同色,所以直接上三维 \(01\) 背包计算每个置换的 \(D(a_j)\) ,再利用引理计算 \(L\) 即可。时间复杂度 \(O(nmS_rS_bS_g)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=65,M=25;
int n,m,p,rn,bn,gn,ps[N];
int fl[N],f[M][M][M];
int qpow(int x,int y){
int re=1;
while(y){
if(y&1) re=re*x%p;
x=x*x%p,y>>=1;
}return re;
}void dp(int sz){
for(int i=rn;~i;i--)
for(int j=bn;~j;j--)
for(int k=gn;~k;k--){
if(i>=sz) f[i][j][k]+=f[i-sz][j][k];
if(j>=sz) f[i][j][k]+=f[i][j-sz][k];
if(k>=sz) f[i][j][k]+=f[i][j][k-sz];
f[i][j][k]%=p;
}
}int get_dp(){
for(int i=1;i<=n;i++) fl[i]=0;
for(int i=0;i<=rn;i++)
for(int j=0;j<=bn;j++)
for(int k=0;k<=gn;k++)
f[i][j][k]=0;
f[0][0][0]=1;
for(int i=1;i<=n;i++){
if(fl[i]) continue;
int nw=i,sum=0;
while(!fl[nw])
sum++,fl[nw]=1,nw=ps[nw];
dp(sum);
}return f[rn][bn][gn];
}int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>rn>>bn>>gn>>m;
n=rn+bn+gn,cin>>p;
for(int i=1;i<=n;i++) ps[i]=i;
int sum=get_dp();
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++) cin>>ps[j];
sum=(sum+get_dp())%p;
}cout<<sum*qpow(m+1,p-2)%p;
return 0;
}
该引理通常和 \(dp\) 紧密相连,其原因在于其他办法难以快速枚举 \(D(a_j)\)。
五、\(Polya\) 定理
我们可以将置换的循环节式写法(为方便描述,使用第一框题中的例子) \(\sigma=(126)(35)(4)\) 看作多个互不相交的置换的乘积,即 \(\sigma=\sigma_1\sigma_2\sigma_3(\) 其中 \(\sigma_1=(_{1\ 2\ 6}^{2\ 6\ 1}),\sigma_2=(_{3\ 5}^{5\ 3}),\sigma_3=(_{4}^{4}))\)。
定理内容如下:
设 \(G\) 是包含 \(p\) 个元素的置换群,用 \(m\) 种颜色给 \(p\) 个元素染色。定义 \(c(g)\) 表示置换 \(g\) 的循环节个数,则方案数为:
\[L=\frac{1}{|G|}\sum\limits_{i=1}^pm^{c(g_i)} \]
通常该定理和欧拉函数结合使用,当然,容易发现 \(m^{c(g_i)}=D(g_i)\),因此可以理解为是一种在特定情况下快速求解 \(D(a_j)\) 的方法。
显然板子了,问题转化为求解 \(\sum\limits_{i=1}^pn^{c(g_i)}\)。由于 \(n\) 过大,同时 \(c(g_i)\) 的可能值很少,所以考虑枚举 \(c(g_i)\)。
考虑到循环节显然等长,长度是 \(len=\gcd(i,n)\)。那么显然有 \(\gcd(\frac{i}{len},\frac{n}{len})=1\),所以长度为 \(len\) 的循环节个数显然有 \(\varphi(\frac{n}{len})\) 个。
根据上述性质,容易发现答案为:
时间复杂度 \(O(t\sqrt n\log n)\)。
\(ps.\) 之所以 \(\frac ni\) 要 \(-1\),是因为本身是要整体 \(\times \frac 1n\)。
//该代码不能在 POJ 上 AC,但是有正确性
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,p,ans;
int phi(int x){
int re=x,c=x;
for(int i=2;i*i<=x;i++){
if(c%i) continue;
re=re/i*(i-1);
while(c%i==0) c/=i;
if(c==1) return re;
}if(c==1) return re;
return re/c*(c-1);
}int qpow(int x,int y){
int re=1;
while(y){
if(y&1) re=re*x%p;
x=x*x%p,y>>=1;
}return re;
}void add(int x){
ans=(ans+phi(x)*qpow(n%p,n/x-1))%p;
}void solve(){
cin>>n>>p,ans=0;
for(int i=1;i*i<=n;i++){
if(n%i) continue;add(i);
(i*i!=n?add(n/i):void());
}cout<<ans<<"\n";
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>t;
while(t--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)