群論基本知識及一些重要定理


群論

一.基本定義

群:給定一個集合$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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM