[SHOI2006]有色圖 題解
題目大意
給出一個 \(n\) 個點的完全圖,有 \(m\) 種顏色可以塗到邊上,定義兩個圖不同當且僅當點通過置換之後每條邊的顏色都相同,問有多少種不同的染色方法。
答案對 \(p\) 取模,\(p\) 為給出的一個質數。
\(n\le 53,m\le 1000,n<p\le 10^9\)
思路
算是我入門 \(\text{Pólya}\) 定理的一個題目了,雖然它很難(倫敦霧 肝了我一天。。。
我們發現這個題目顯然需要用 \(\text{Pólya}\) 定理解決,但是我們發現似乎並不好考慮邊的置換,所以我們考慮點的置換帶來的邊的置換。
我們考慮到 \(\text{Pólya}\)
我們發現對於一條邊,它在點置換裡面只有兩種情況:
- 它兩端頂點在一個大小為 \(b\) 的迴圈裡面
我們可以考慮把這 \(b\) 個點扔到一個環,那麼一次置換其實就是旋轉一次。然後我們發現,如果 \(b\) 為奇數的話,那麼,旋轉 \(b\) 次顯然就可以轉回到原來的位置,所以迴圈節個數就是 \(\frac{\frac{b(b-1)}{2}}{b}=\frac{b}{2}\)
如果 \(b\) 為偶數的話,那麼,我們有些邊旋轉 \(180^\circ\) 就可以回到原來的位置,迴圈長度就是 \(\frac{b}{2}\),所以迴圈節個數就是 \(\frac{\frac{b(b-1)}{2}-\frac{b}{2}}{b}+\frac{\frac{b}{2}}{\frac{b}{2}}=\frac{b}{2}\) 。
綜上,迴圈節個數就是 \(\frac{b}{2}\) 個。
- 它兩端頂點分別在大小為 \(b1,b2\) 的迴圈裡面
可以想到這兩個迴圈合成的迴圈長度為 \(\text{lcm}(b1,b2)\) ,於是迴圈節個數就是 \(\dfrac{b1\times b2}{\text{lcm}(b1,b2)}=\gcd(b1,b2)\)
於是我們發現,我們只需要知道每個迴圈節大小就好了。
不過還有一個問題,我們還需要知道有多少個置換為當前狀態(每個迴圈節的大小),考慮我們有 \(k\) 個迴圈,迴圈節大小分別為 \(l_1,l_2,...,l_k\) 。
首先我們假設對每一個點編號為所屬迴圈的數字,那麼,實際上就是重排列個數,即 \(\frac{n!}{\prod_{i=1}^{k}l_i!}\) ,不過我們一個迴圈節裡面還可以圓排列,於是,還需要乘上 \(\prod_{i=1}^{k} (l_i-1)!\) 。不過我們發現還是有問題,因為我們這樣會算重,因為迴圈之間的順序並沒有影響,我們這樣計算的話相同大小的迴圈節我們實際上就賦上排列了,所以還需要除上 \(c!\) ,其中 \(c\) 是迴圈節大小相同的個數(大小為當前迴圈節大小)。
然後我們就可以求出答案了,時間複雜度玄學,可能合法方案比較少吧。
\(\texttt{Code}\)
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define MAXN 55
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,m,p,ans,rec[MAXN],fac[MAXN];
int gcd (int a,int b){return !b ? a : gcd (b,a % b);}
int qkpow (int a,int b){
int res = 1;for (;b;b >>= 1,a = 1ll * a * a % p) if (b & 1) res = 1ll * res * a % p;
return res;
}
void calc (int k){
int sum = 0,mul = 1,now = 1;
for (Int i = 1;i <= k;++ i) sum = (sum + rec[i] / 2) % (p - 1),mul = 1ll * mul * rec[i] % p;
for (Int i = 1;i <= k;++ i)
for (Int j = i + 1;j <= k;++ j)
sum = (sum + gcd (rec[i],rec[j])) % (p - 1);
for (Int i = 2;i <= k;++ i){
if (rec[i] != rec[i - 1]) mul = 1ll * mul * fac[now] % p,now = 0;
++ now;
}
mul = 1ll * mul * fac[now] % p;
mul = 1ll * fac[n] * qkpow (mul,p - 2) % p;
ans = (ans + 1ll * qkpow (m,sum) * mul % p) % p;
}
void dfs (int k,int Sum,int down){
if (Sum == 0) return calc (k - 1);
if (Sum < down) return ;
for (Int i = down;i <= Sum;++ i){
rec[k] = i;
dfs (k + 1,Sum - i,i);
}
}
signed main(){
read (n,m,p);
fac[0] = 1;for (Int i = 1;i <= n;++ i) fac[i] = 1ll * fac[i - 1] * i % p;
dfs (1,n,1);ans = 1ll * ans * qkpow (fac[n],p - 2) % p;write (ans),putchar ('\n');
return 0;
}