【學習筆記】Burnside 定理和 Polya 定理
基本公式:
公式:
Burnside 定理:
\[\dfrac{1}{|G|}\sum_{g\in G} M(g) \]其中 \(M (g)\) 表示在置換 \(g\) 的作用下,不動點的數量,\(G\) 代表置換群,也可以理解為所有的置換構成的集合。
Polya 定理:
\[\dfrac{1}{|G|}\sum_{g\in G}m^{c(g)} \]其中 \(c(g)\) 代表置換 \(g\) 的迴圈的個數
其實根據這倆公式就能發現
\[M(g) = m^{c(g)} \]其實這是兩個很神奇的公式,它們解決的問題就是類似於求多少種本質不同的方案的個數,這兩者之間也是可以進行互換。
公式解釋:
比如最經典的項鍊染色:
所謂的置換數就是旋轉的個數也就是所謂的能進行操作讓他變化的運算元
所謂的不動點:
就是指經過了某一次操作也就是置換他還是他
所謂的迴圈個數:
有多少組位置,經過了一次置換之後使得這些位置上的數還是這些位置原本的那些數,只不過最多位置有所變化。
經典例題:
有 \(m\) 種顏色的珠子,要用它搞出一個長度為 \(n\) 的項鍊,求本質不同的方案數。這裡的本質不同是指通過旋轉或對稱無法相同。
旋轉
旋轉的話假設我們旋轉了 \(k\) 個,那麼就是說看不動點的數量:因為不動點的數量和迴圈個數可以互換。假設旋轉了 \(k\) 要使得整個項鍊還是它,那麼就意味著整個序列存在一個最長為 \(gcd(n,k)\)
根據 Burnside 定理,所以最後的答案就是
\[\sum_{k=1}^n{m^{gcd(n,k)}} \]考慮所謂的 \(k=0\) 好像是所謂的 \(m^n\) 種可能都是可以的,但是我們考慮一個都不旋轉有啥區別嗎,需要統計上嗎,不需要啊!!!(考慮了大概二十分鐘,真的無語。)
對稱
分兩種情況:
(1)\(n\) 為奇數:
那麼有可能成為答案的翻轉就是以某一個點和其對應邊的中點的連線作為對稱軸
那麼總共有 \(n\) 個點,也就意味著我們可以選擇 \(n\) 個點來進行旋轉,也就是意味著我們的置換個數為 \(n\),因為置換數就是指的操作個數。這裡來考慮 \(polya\) 定理,也就是求某一個置換的迴圈個數,這種也就非常好想了。在對稱的條件下啥是迴圈:只有可能是相對應的兩個點是一個迴圈啊,也就是所謂的只可能是 \(a-b,b-a\) 這種的可能,那麼去除我們選擇的那個點,總共有 \(n-1\) 個點,也就是總共有 \(\frac{n-1}{2}\) 個迴圈,但是考慮所謂的迴圈:我們選擇的點經過對稱會變成我們選擇的點,也就是不動,但是在置換裡就是一個 \(a,a\) 這也算一個迴圈啊,所以迴圈數就是 \(\frac{n+1}{2}\)那麼根據 \(polya\) 定理,答案數就是
\[\sum_{k=1}^nm^{\frac{n+1}{2}} = n \times m^{\frac{n+1}{2}} \](2)\(n\) 為偶數:
那麼考慮有可能成為答案的翻轉 1.選擇兩個相對應的點的連線 2.選擇兩個相對應的邊的中點的連線
1.選擇兩個相對應的點的連線作為對稱軸來對稱,那麼這種對稱軸的選法不就總共有 \(n/2\) 種嗎,也就是所謂的我們的置換個數為 \(n/2\),然後就考慮每一種置換總共有多少個迴圈。其實也就是跟上面一樣的,只有可能是 \(a-b,b-a\) 這種形式,所以最多有 \((n-2)/2\) 個迴圈,但是我們還選擇了兩個點所以就有多出來的 \(2\) 個迴圈,所以就是有 \((n+2)/2\) 個迴圈,所以根據 \(polya\) 定理,答案數就是
\[\sum_{k=1}^{n/2} m^{\frac{n+2}{2}} = \dfrac{n}{2} \times m^{\frac{n+2}{2}} \]2.選擇兩個對應的邊有 \(n/2\) 種選法,也就是有 \(n/2\) 種對稱的方案,所以置換個數就是 \(n/2\)。那麼考慮一個置換有多少個迴圈呢,很明顯只有可能是 \(a-b,b-a\) 的形式,所以有 \(n/2\) 個迴圈,所以根據 \(polya\) 定理可得:
\[\sum_{k=1}^{n/2}m^{\frac{n}{2}} =\dfrac{n}{2}\times m^{\frac{n}{2}} \]注意我們的結果就是說要把這些所有的情況合併到一起,這裡分類討論只是為了方便去分析。所以最後的答案就是分奇偶之後的答案之和除以置換也就是運算元量,那麼置換很神奇的能發現:
奇數:\(n + n = 2n\)
偶數:\(n + \frac{n}{2} + \frac{n}{2} = 2n\)
所以分類統計答案最後直接除以 \(2n\) 然後輸出就好了
程式碼
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
long long quick_power(long long a,long long b){
long long res = 1;
while(b){
if(b&1){
res *= a;
}
b >>=1;
a*=a;
}
return res;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
long long n,m;
while(1){
cin>>n>>m;
if(n == -1)
break;
if(n == 0){
printf("%d\n",0);
continue;
}
long long ans = 0;
for(long long i=1; i<=n; i++){
ans += quick_power(m,__gcd(i,n));
}
if(n & 1) //奇數
ans += n * quick_power(m,(n+1)/2);
else //偶數
ans += (n/2) * quick_power(m,((n+2)/2)) + (n/2) * quick_power(m,n/2);
ans /= 2*n;
printf("%lld\n",ans);
}
return 0;
}
理解了之後就超級好寫的一份程式碼