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

群论

一.基本定义

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

①.封闭性:对于任意$a,b\in G$,一定存在$c\in G$,使得$a·b=c$

②.结合律:对于任意$a,b,c\in G$,有$(a·b)·c=a·(b·c)$

③.单位元:存在$e\in G$,使得对任意$a\in G$,有$a·e=e·a=a$

④.逆元:对任意$a\in G$,均存在$b\in G$,使得$a·b=e$,其中$b$称作$a$的逆元,记作$a^{-1}=b$

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

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

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

二.基本性质:

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

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

③.对$a,b,c\in G$,若$ab=ac$,则$b=c$

④.$(abcd...m)^{-1}=m^{-1}l^{-1}...a^{-1}$

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

三.置换群:

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

1.置换的定义:

记一个序列{$a_{n}$}={$a_{1},a_{2}...a_{n}$}是1~n的一个排列

定义一个置换$p=\begin{pmatrix} 1&2&...&n\\a_{1}&a_{2}&...&a_{n} \end{pmatrix}$

其含义是用$a_{1}$取代原来的元素$1$,用$a_{2}$取代原来的元素$2$...用$a_{n}$取代原来的元素$n$

置换的运算定义如下:

设两个元素$p_{1}=\begin{pmatrix} 1&2&...&n\\a_{1}&a_{2}&...&a_{n} \end{pmatrix}$,$p_{2}=\begin{pmatrix} 1&2&...&n\\b_{1}&b_{2}&...&b_{n} \end{pmatrix}$,则运算$p_{1}p_{2}$过程如下:

$p_{1}p_{2}=\begin{pmatrix} 1&2&...&n\\a_{1}&a_{2}&...&a_{n} \end{pmatrix}\begin{pmatrix} 1&2&...&n\\b_{1}&b_{2}&...&b_{n} \end{pmatrix}=\begin{pmatrix} 1&2&...&n\\a_{1}&a_{2}&...&a_{n} \end{pmatrix}\begin{pmatrix} a_{1}&a_{2}&...&a_{n}\\b_{a_{1}}&b_{a_{2}}&...&b_{a_{n}} \end{pmatrix}=\begin{pmatrix} 1&2&...&n\\b_{a_{1}}&b_{a_{2}}&...&b_{a_{n}} \end{pmatrix}$

同理可以看出:如果我们计算$p_{2}p_{1}$,则得到的结果应当是$\begin{pmatrix} 1&2&...&n\\a_{b_{1}}&a_{b_{2}}&...&a_{b_{n}} \end{pmatrix}$

2.置换群的定义:

那么定义置换群$G=${$p_{1},p_{2}...p_{m}$}

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

3.循环的定义:

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

记$(a_{1},a_{2}...a_{m})=\begin{pmatrix} a_{1}&a_{2}&...&a_{m}\\a_{2}&a_{3}&...&a_{1} \end{pmatrix}$

稍微解释一下:原本的一个置换可以写作$\begin{pmatrix} 1&2&...&n\\a_{1}&a_{2}&...&a_{n} \end{pmatrix}$,那么我们可以把这个置换群写成这个形式:

$\begin{pmatrix} 1&a_{1}&...&n\\a_{1}&a_{p}&...&a_{q} \end{pmatrix}$也就是说我们直接把一个置换连续相接,就能得出一个循环,这样得出的循环就是上面那个形式

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

举个例子:$S_{3}=${$(1)(2)(3),(2 3),(1 2),(1 3),(1 2 3),(1 3 2)$}

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

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

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

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

那么对于任意$p_{i}\in S_{n}$,我们均可以把$p_{i}$写成若干互不相交的循环乘积形式,即:

$p_{i}=(a_{1} a_{2}...a_{k_{1}})(b_{1} b_{2}...b_{k_{2}})...(h_{1} h_{2}...h_{k_{l}})$

其中满足$k_{1}+k_{2}+...+k_{l}=n$

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

所以一个置换$p_{i}$可分解成的格式是$(1)^{c_{1}}(2)^{c_{2}}...(n)^{c_{n}}$

显然有一个表达式:$\sum_{i=1}^{n}i*c_{i}=n$

5.共轭类:

在$S_{n}$中有相同格式的置换全体,称作与该格式相对应的共轭类

 定理:$S_{n}$中属于格式$(1)^{c_{1}}(2)^{c_{2}}...(n)^{c_{n}}$的共轭类的元素个数为:$\frac{n!}{\prod_{i=1}^{n}c_{i}!\prod_{i=1}^{n}i^{c_{i}}}$

6.k不动置换类:

设$G$是$S_{n}$的一个子群,设$k\in [1,n]$,$G$中使k不动的置换全体,称作$G$中使k不变的置换类,简称k不动置换类,记作$Z_{k}$

不难看出,$Z_{k}$是$G$中所有含有“因子”$(k)$的置换全体

7.等价类:

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

定理:对任意$k\in [1,n]$,有:$|E_{k}||Z_{k}|=|G|$

四.burnside引理:

内容:设$G$是1~n上的一个置换群,则$G$在n上引出的等价类的数量为$\frac{1}{|G|}[c_{1}(p_{1})+c_{1}(p_{2})+...+c_{1}(p_{|G|})]$

人话:一个置换群$G$中共有$|G|$个置换,每个置换$p_{i}$都有一个不动点数量$c_{1}(p_{1})$,那么$G$的等价类数量为所有不动点数量的平均值

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

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

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

 

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

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

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

直接套用burnside引理可得:$l=\frac{1}{4}*(16+2+4+2)=6$

 五.Polya定理:

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

$l=\frac{1}{|G|}[m^{c(p_{1})}+m^{c(p_{2})}+...+m^{c(p_{|G|})}]$

其中$c(p_{i})$表示置换$p_{i}$的循环个数

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

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

$p_{1}=(1)(2)(3)(4)$

$p_{2}=(4 3 2 1)$

$p_{3}=(1 3)(2 4)$

$p_{4}=(1 2 3 4)$

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

那么可推知$c(p_{1})=4,c(p_{2})=1,c(p_{3})=2,c(p_{4})=1$

所以最后的染色方案数为$l=\frac{1}{4}(2^{4}+2^{1}+2^{2}+2^{1})=6$

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

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

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

六.例题:

luogu 4980

首先是polya定理没错啦

接下来考虑怎么做

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

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

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

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

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

$ans=\frac{\sum_{i=1}^{n}n^{gcd(n,i)}}{n}$

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

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

你需要莫比乌斯反演

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

$\frac{1}{n}\sum_{d|n}\sum_{i=1}^{n}[gcd(n,i)==d)]n^d$

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

再变个形,就是这样:

$\frac{1}{n}\sum_{d|n}\sum_{i=1}^{\frac{n}{d}}gcd[(\frac{n}{d},i)==1]n^d$

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

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

$\frac{1}{n}\sum_{d|n}\phi(\frac{n}{d})n^d$

这就是答案!

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

可能你没有看出优化在哪里,给出一点解释:枚举一个数的约数复杂度是$O(\sqrt{n})$级别的,而求解$phi(i)$则可以一部分预处理,另一部分较大的在线计算,我选择预处理前$10^7$的$phi$,然后剩下的部分暴力计算,考虑到n的范围小于等于$10^9$,因此暴力计算的部分是很有限的,总复杂度$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 @ 2019-05-31 20:56  lleozhang  Views(7080)  Comments(2Edit  收藏  举报
levels of contents