[bzoj1488][HNOI2009]圖的同構——Polya定理
阿新 • • 發佈:2019-01-22
題目大意
求兩兩互不同構的含n個點的簡單圖有多少種。
簡單圖是關聯一對頂點的無向邊不多於一條的不含自環的圖。
a圖與b圖被認為是同構的是指a圖的頂點經過一定的重新標號以後,a圖的頂點集和邊集能完全與b圖一一對應。
題解
這個題是學習了Polya定理和群論以後的練手題,但是推了好久並沒有推出來。。。。真的是太難辣。。。
首先我先說一下我錯誤的想法:
很容易就把這個題轉化成了給\(K_n\)的完全圖上的邊進行二著色的問題,然後,由於在組合數學課程中經常接觸到多邊形著色,所以我就把這個題錯誤的轉化成了在一個正\(\frac{n(n-1)}{2}\)邊形的頂點上進行二著色的問題。然而對於n=1,2,3這種方法都是可行的,但是到了n=4的情況,這種方法就不可行了。我仔細觀察了一下,發現這個轉化不符合滿足純粹性和完備性
然後就說一下正解吧。
首先我們考慮n = 4的情況,對於\(K_4\)進行二著色,我們很容易發現,由於圖是可以任意扭轉的,所以它的置換群實際上是一個對稱群!
那麼對於點的每一個置換我們要計算對應的邊的置換。
在一個置換中,考察一條邊,如果這條邊的兩個節點位於相同的迴圈中,那麼我們可以得出邊的迴圈個數是點的迴圈個數的一半。
如果這條邊的兩個節點位於不同的迴圈中,那麼我們畫一畫圖就可以知道如果點的迴圈個數分別是a, b,那麼邊的迴圈個數就是gcd(a,b)。
根據這樣的方法,我們就可以把點的置換轉化為邊的置換了。
下面的任務就是要列舉置換。
如果直接暴力,複雜度很高。
我們考慮這樣的列舉(回溯)方法:
依次考慮每一種階的迴圈的個數,然後暴力dfs即可。
現在假設我們已經列舉好了一個置換,那麼這種置換的個數根據一些基本的排列組合知識,可以知道是:
\[\frac{n!}{\prod_{i = 1}^{cnt} Val_i * Num_i !}\]
稍微解釋一下這個式子。除以\(Val_i\)是因為圓形排列,除以\(Num_i\)是因為同階迴圈的重複排列。
根據Polya定理,等價類的個數就是:
\[l = \frac{1}{N!} * \sum 2^m\]
參考題解
事實上,這個題還有一個變態的做法:
就是上OEIS上查詢通項公式。。。。
程式碼
#include <bits/stdc++.h> const int mod = 997; const int maxn = 1010; using namespace std; int n, cnt, ans; int two[maxn], factor[maxn], val[maxn], num[maxn]; int pow(int n, int m) { int ans = 1; int b = m; while(b) { if(b & 1) ans = (ans * n) % mod; b >>= 1; n = (n*n) % mod; } return ans; } int inv(int n) { return pow(n, mod-2); } int gcd(int a, int b) { if(b == 0) return a; else return gcd(b, a%b) % mod; } void init() { factor[0] = factor[1] = two[0] = 1; for(int i = 2; i <= 1000; i++) { factor[i] = ((i % mod) * factor[i-1]) % mod; } for(int i = 1; i <= 1000; i++) { two[i] = (two[i-1] * 2) % mod; } } void dfs(int now_num, int left) { if(left == 0) { int sum1 = 0, sum2 = 1; //sum1:這一種置換的迴圈個數 //sum2:這一種置換的個數 for(int i = 1; i <= cnt; i++) { sum1 += (num[i] * (num[i] - 1) / 2 * val[i]) + (val[i]/2 * num[i]); //前一部分:對於同一種迴圈中的不同迴圈的邊的處理 for(int j = i + 1; j<= cnt; j++) { sum1 += num[i] * num[j] * gcd(val[i], val[j]); } } for(int i = 1; i <= cnt; i++) { sum2 = (sum2 * pow(val[i], num[i])%mod*factor[num[i]])%mod; } sum2 = inv(sum2) * factor[n] % mod; ans = (ans + pow(2, sum1) * sum2 % mod) % mod; } if(now_num > left) return; dfs(now_num+1, left); //這裡的dfs放到外面可以降低常數: //如果放在for迴圈裡面,那麼num陣列中會多出許多0 //浪費時間。 for(int i = 1; i * now_num <= left; i++) { val[++cnt] = now_num, num[cnt] = i; dfs(now_num+1, left - i * now_num); cnt--; //回溯 } } int main() { scanf("%d", &n); init(); dfs(1, n); ans = (ans * inv(factor[n])) % mod; printf("%d", ans); return 0; }