传送门:bzoj1478
题解
设点数为 , 种颜色。很容易想到点的置换方式有 种,直接枚举判断同构复杂度太高显然不行。
由 定理,可以通过求每个边转置群 循环组个数 ,总转置集合为 ,答案即为
复杂度还是没有降下来,但 学习到了一个巧妙的方法(超神奇!):
考虑利用点置换转化为边置换,且方案只与点转置中每个循环组大小有关,而与具体转置方法无关。
枚举点转置的循环组(有向边形成的环,下文均称其为环),观察性质可以分别得到环内以及环之间的边的转置的循环组个数。这里的转置均考虑为环上的旋转操作。
设环的大小为 ,即循环组内点数。则环内的边循环组个数为 。设另一个环的大小为 ,则这两个环之间连边的循环组个数为 (一共 条边,旋转 次后回到初始状态,循环组大小均为 ,所以个数为 )。
所以循环组总个数:
所以 枚举每个循环组大小(按递降顺序排列,保证每种组合只统计到一次),复杂度统计类似于codevs2549 自然数和分解 的答案, 时,会枚举 次。
现在每一种组合的答案都可以计算出来,只需要考虑计算每种组合在所有转置中出现的次数:
把 个点随机放入 个环内的方案数: ,每个环内放好第一个点以后可以任意排列,所以要乘上 。
但考虑 相同的环方案数算重了,假设 表示值为 的环的个数,还需要除去 ,这里设 。
所以每种组合出现次数为:
所以每次枚举到一种组合,把答案可以加上:
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,mod,fac[60],cnt[60],v[60];
int gcd[60][60],ans,bs;
ll ss;
inline int GCD(int x,int y){return y==0?x:GCD(y,x%y);}
inline int fp(int x,ll y)
{
int re=1;
for(;y;y>>=1,x=1ll*x*x%mod)
if(y&1) re=1ll*re*x%mod;
return re;
}
inline void work(int num)
{
int i,j;bs=1,ss=0;
memset(cnt,0,sizeof(cnt));
for(i=1;i<=num;++i) cnt[v[i]]++;
for(i=1;i<=n;++i) bs=1ll*bs*fac[cnt[i]]%mod;
for(i=1;i<=num;++i) bs=1ll*bs*v[i]%mod;
for(i=1;i<=num;++i){
ss+=(v[i]>>1);
for(j=i+1;j<=num;++j) ss+=gcd[v[i]][v[j]];
}
ans=(ans+1ll*fac[n]*fp(bs,mod-2)%mod*fp(m,ss)%mod)%mod;
}
inline void dfs(int mxv,int num,int sum)
{
if(sum==n) {work(num);return;}
for(int i=1;i<=mxv && i+sum<=n;++i){
v[num+1]=i;dfs(i,num+1,sum+i);
}
}
int main(){
int i,j;
scanf("%d%d%d",&n,&m,&mod);
for(i=1;i<=n;++i){
gcd[i][i]=i;
for(j=i+1;j<=n;++j)
gcd[i][j]=gcd[j][i]=GCD(i,j);
}
fac[0]=fac[1]=1;
for(i=2;i<=n;++i) fac[i]=1ll*fac[i-1]*i%mod;
dfs(n,0,0);
printf("%d\n",1ll*ans*fp(fac[n],mod-2)%mod);
}