群论基本知识及一些重要定理

群论

一.基本定义

群:给定一个集合G={a,b,c...}和集合上的二元运算"·",要求满足下面四个条件

①.封闭性:对于任意a,bG,一定存在cG,使得a·b=c

②.结合律:对于任意a,b,cG,有(a·b)·c=a·(b·c)

③.单位元:存在eG,使得对任意aG,有a·e=e·a=a

④.逆元:对任意aG,均存在bG,使得a·b=e,其中b称作a的逆元,记作a1=b

如果一个集合满足这个条件,那么就称这个集合是在运算·下的一个群

子群:设G是一个群,HG的一个子集,且H在相同意义下仍然构成一个群,那么称HG的一个子群

接下来将运算a·b简记为ab

二.基本性质:

①.一个群的单位元是唯一的

②.群中任意元素的逆元是唯一的

③.对a,b,cG,若ab=ac,则b=c

④.(abcd...m)1=m1l1...a1

(这里做一个说明:群的定义及性质中均没有要求交换律,因此不要想当然地在群运算中进行交换操作!)

三.置换群:

(接下来的内容有个人理解成分在内,如果有不准确的部分请及时指出,谢谢!)

1.置换的定义:

记一个序列{an}={a1,a2...an}是1~n的一个排列

定义一个置换p=(12...na1a2...an)

其含义是用a1取代原来的元素1,用a2取代原来的元素2...用an取代原来的元素n

置换的运算定义如下:

设两个元素p1=(12...na1a2...an),p2=(12...nb1b2...bn),则运算p1p2过程如下:

p1p2=(12...na1a2...an)(12...nb1b2...bn)=(12...na1a2...an)(a1a2...anba1ba2...ban)=(12...nba1ba2...ban)

同理可以看出:如果我们计算p2p1,则得到的结果应当是(12...nab1ab2...abn)

2.置换群的定义:

那么定义置换群G={p1,p2...pm}

 不难发现,n个元素的一个置换与1~n的一个排列相对应,因此由1~n的全排列所对应的n!个置换可以构成一个群,记作Sn,称Sn为n个文字的对称群(|Sn|=n!

3.循环的定义:

但是我们发现,每次写一个置换太复杂了,因此我们给出一个简单记法:

(a1,a2...am)=(a1a2...ama2a3...a1)

稍微解释一下:原本的一个置换可以写作(12...na1a2...an),那么我们可以把这个置换群写成这个形式:

(1a1...na1ap...aq)也就是说我们直接把一个置换连续相接,就能得出一个循环,这样得出的循环就是上面那个形式

但是,一个循环中不一定会出现所有n个元素,而且一个置换可能需要由大量这种循环来构成

举个例子:S3={(1)(2)(3),(23),(12),(13),(123),(132)}

可以发现,每个元素不一定会出现在每个循环之中,原因是如果一个元素i满足i=ai,那么这个元素就不必(也无法)写入循环了

而且,如果对于每个i都有ai=i,那么肯定不能全都省略,因此对于这种由多个循环组成的置换我们一般把它写成一个循环乘积的形式。

若一个循环的元素个数为k,我们称这个循环为k阶循环

4.一个置换的循环表示方法:

那么对于任意piSn,我们均可以把pi写成若干互不相交的循环乘积形式,即:

pi=(a1a2...ak1)(b1b2...bk2)...(h1h2...hkl)

其中满足k1+k2+...+kl=n

设所有这些循环中i阶循环出现的次数为ci,那么我们记作(i)ci

所以一个置换pi可分解成的格式是(1)c1(2)c2...(n)cn

显然有一个表达式:i=1nici=n

5.共轭类:

Sn中有相同格式的置换全体,称作与该格式相对应的共轭类

 定理:Sn中属于格式(1)c1(2)c2...(n)cn的共轭类的元素个数为:n!i=1nci!i=1nici

6.k不动置换类:

GSn的一个子群,设k[1,n]G中使k不动的置换全体,称作G中使k不变的置换类,简称k不动置换类,记作Zk

不难看出,ZkG中所有含有“因子”(k)的置换全体

7.等价类:

给出一个置换群GSn的一个子群,设k,l[1,n],且存在置换pG,使得在置换p的作用下能够将k变为l,则称k,l属于同一个等价类,因此1~n的所有整数可以按照G的置换分成若干个等价类,一个数i所属的等价类记作Ei

定理:对任意k[1,n],有:|Ek||Zk|=|G|

四.burnside引理:

内容:设G是1~n上的一个置换群,则G在n上引出的等价类的数量为1|G|[c1(p1)+c1(p2)+...+c1(p|G|)]

人话:一个置换群G中共有|G|个置换,每个置换pi都有一个不动点数量c1(p1),那么G的等价类数量为所有不动点数量的平均值

可能你并不是很懂,我们举个例子:

一个正方形均分成四个格子,用两种颜色对这四个格子进行染色,经过旋转可以重合的两种染色方案记作同一种方案,问有多少种不同的方案?

首先我们可以看到,不考虑任何限制,一共有16种染色方案:

 

这是初始状态,接下来我们进行计算:

我们认为一个置换是将一个状态通过旋转一定角度获得另一种状态,那么我们可以得到一个置换群

那么最后的答案就是这个置换群的不同等价类个数

直接套用burnside引理可得:l=14(16+2+4+2)=6

 五.Polya定理:

内容:设G是n上的一个置换群,用m种颜色涂染这n个对象,其中如果两种方案可以在群G作用下互相转化,则称这两种方案为同一种方案,那么总方案数的表达式为:

l=1|G|[mc(p1)+mc(p2)+...+mc(p|G|)]

其中c(pi)表示置换pi的循环个数

我们仍然用上面正方形染色的例子,但这次先对每个格子进行编号:

这个正方形的置换一共有四种:

p1=(1)(2)(3)(4)

p2=(4321)

p3=(13)(24)

p4=(1234)

分别对应不旋转,顺时针旋转,旋转180和逆时针旋转

那么可推知c(p1)=4,c(p2)=1,c(p3)=2,c(p4)=1

所以最后的染色方案数为l=14(24+21+22+21)=6

单纯从这个角度讲,burnside引理和polya定理处理的问题其实是一样的

但是仅仅在如此小规模的问题中,两者的效率差异已经体现得非常明显了:burnside引理需要求出每一种染色方案,一共需要找16种,然后在对这些方案之间进行置换,而polya定理仅需要找出原图中的四种置换即可

因此polya的效率相对更高一些

六.例题:

luogu 4980

首先是polya定理没错啦

接下来考虑怎么做

按照套路,首先我们应该找出一个置换群

不难发现,这个置换群的大小应该是n,因为一个长度为n的圆周一共有n种旋转状态

接下来,我们考虑每个置换的循环个数

通过打表严谨的推理可知,第i个置换的循环节个数应该为gcd(n,i)

于是我们立刻可以写出表达式:

ans=i=1nngcd(n,i)n

然而这个东西求一次就是O(n)的,很显然会T飞

所以我们必须处理一下这个问题...

你需要莫比乌斯反演

我们调整一下,可以看到,原表达式等价于下面这个形式:

1nd|ni=1n[gcd(n,i)==d)]nd

这样你是不是已经很熟悉了?

再变个形,就是这样:

1nd|ni=1ndgcd[(nd,i)==1]nd

答案不就呼之欲出了?后面那个不就是欧拉函数的定义嘛

于是我们立刻可以写出最后的表达式

1nd|nϕ(nd)nd

这就是答案!

(话说为什么要在群论里出现莫比乌斯反演啊喂)

可能你没有看出优化在哪里,给出一点解释:枚举一个数的约数复杂度是O(n)级别的,而求解phi(i)则可以一部分预处理,另一部分较大的在线计算,我选择预处理前107phi,然后剩下的部分暴力计算,考虑到n的范围小于等于109,因此暴力计算的部分是很有限的,总复杂度O(

贴代码:

复制代码
#include <cstdio>
#define ll long long
using namespace std;
const ll mode=1000000007;
ll phi[10000005];
ll pri[10000005];
bool used[10000005];
int cnt=0;
int T;
ll n;
ll pow_mul(ll x,ll y)
{
    ll ans=1;
    while(y)
    {
        if(y&1)ans=ans*x%mode;
        x=x*x%mode,y>>=1;
    }
    return ans;
}
void init()
{
    phi[1]=1;
    for(ll i=2;i<=10000000;i++)
    {
        if(!used[i])pri[++cnt]=i,phi[i]=i-1;
        for(ll j=1;j<=cnt&&i*pri[j]<=10000000;j++)
        {
            used[i*pri[j]]=1;
            if(i%pri[j]==0)
            {
                phi[i*pri[j]]=phi[i]*pri[j];
                break;
            }
            phi[i*pri[j]]=phi[i]*(pri[j]-1);
        }
    }
}
ll get_phi(ll x)
{
    if(x<=10000000)return phi[x];
    ll ret=x;
    for(ll i=2;i*i<=x;i++)
    {
        if(x%i==0)
        {
            ret/=i;
            ret*=(i-1);
            while(x%i==0)x/=i;
        }
    }
    if(x!=1)ret/=x,ret*=(x-1);
    return ret;
}
void get_ans(ll x)
{
    ll sum=0;
    for(ll i=1;i*i<=x;i++)
    {
        if(x%i==0)
        {
            sum=(sum+pow_mul(x,i-1)*get_phi(x/(ll)i)%mode)%mode;
            ll di=x/i;
            if(di!=i)sum=(sum+pow_mul(x,di-1)*get_phi(i)%mode)%mode;
        }
    }
    printf("%lld\n",sum);
}
int main()
{
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld",&n);
        get_ans(n);
    }
    return 0;
}
复制代码

 

posted @   lleozhang  Views(7277)  Comments(2Edit  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
levels of contents
点击右上角即可分享
微信分享提示