1. 程式人生 > 實用技巧 >[SHOI2006]有色圖 題解

[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}\)

定理的式子長成這個樣子: \(\frac{1}{|G|}\sum_{i\in G} m^{d(i)}\) 其中 \(d(i)\) 表示置換 \(i\) 的迴圈節個數,於是問題就是如何對於一個邊置換求出它的迴圈節個數(實際上我們需要通過點置換算出這個)。

我們發現對於一條邊,它在點置換裡面只有兩種情況:

  • 它兩端頂點在一個大小為 \(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;
}