题目大意
给出一个 (n) 个点的完全图,有 (m) 种颜色可以涂到边上,定义两个图不同当且仅当点通过置换之后每条边的颜色都相同,问有多少种不同的染色方法。
答案对 (p) 取模,(p) 为给出的一个质数。
(nle 53,mle 1000,n<ple 10^9)
思路
算是我入门 ( ext{Pólya}) 定理的一个题目了,虽然它很难(伦敦雾 肝了我一天。。。
我们发现这个题目显然需要用 ( ext{Pólya}) 定理解决,但是我们发现似乎并不好考虑边的置换,所以我们考虑点的置换带来的边的置换。
我们考虑到 ( ext{Pólya}) 定理的式子长成这个样子: (frac{1}{|G|}sum_{iin 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) 的循环里面
可以想到这两个循环合成的循环长度为 ( ext{lcm}(b1,b2)) ,于是循环节个数就是 (dfrac{b1 imes b2}{ ext{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) 是循环节大小相同的个数(大小为当前循环节大小)。这里解释一下,只能在同大小的循环节里面交换是因为大小不同的循环节你没有办法交换点的编号。
然后我们就可以求出答案了,时间复杂度玄学,可能合法方案比较少吧。
( exttt{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 ('
');
return 0;
}