题目链接
http://acm.sgu.ru/problem.php?contest=0&problem=282
思路
显然有N!N!个边的置换,枚举边的置换肯定不可行。
因此,我们来考虑点置换和边置换的关系。
首先,考虑一条边(x,y)(x,y)。
如果xx和yy在点置换中的同一循环节中,设这个循环节长度为LL。
若LL为奇数,则(x,y)(x,y)所处边置换中循环节长度为LL,一共有C2LCL2条边,所以循环节的个数为C2LL=L−12CL2L=L−12个。
若LL为偶数,则(x,y)(x,y)所处边置换,出来上述长度为LL的情况,还有一种情况:
如果x,yx,y在循环节中正好相距L2L2,那么循环节长度为L2L2这样的循环节一共有L2L2个,循环节的个数为C2L−L2L+1=L2CL2−L2L+1=L2个循环节。
再来考虑x,yx,y不在一个循环节的情况,设循环节长度分别为L1,L2L1,L2。
显然,(x,y)(x,y)所在置换的循环节长度为lcm(L1,L2)lcm(L1,L2),这样的循环节个数为L1L2lcm(L1,L2)=gcd(L1,L2)L1L2lcm(L1,L2)=gcd(L1,L2)个。
若一个点置换的循环节长度分别为L1,L2,⋯,LKL1,L2,⋯,LK,那么边置换的循环节长度为:
接下来我们求满足循环节长度为L1,L2,⋯,LKL1,L2,⋯,LK的边置换个数。
这就相当于将1⋯N1⋯N放入大小为L1,L2,⋯,LKL1,L2,⋯,LK的环中,求总方案数。
显然,如LL不重复,那么总方案数为N!L1L2⋯LKN!L1L2⋯LK,这里不给出证明了。
若LL有重复,假设LL中有tt种不同的值,第ii种值的个数为kiki,那么总方案数(也就是满足条件的边置换个数)为:
最终的答案为:
代码
#include <cstdio>
const int maxn=53;
int fac[maxn+1],l[maxn+1],n,m,p,ans;
int gcd(int x,int y)
{
return y?gcd(y,x%y):x;
}
inline int quickpow(int a,int b,int mo)
{
int res=1;
while(b)
{
if(b&1)
{
res=1ll*res*a%mo;
}
a=1ll*a*a%mo;
b>>=1;
}
return res;
}
inline int getans(int x)
{
int c=0,s=1,cnt=0;
for(register int i=1; i<=x; ++i)
{
c+=l[i]/2;
for(register int j=i+1; j<=x; ++j)
{
c+=gcd(l[i],l[j]);
}
s=(1ll*s*l[i])%p;
if(l[i]!=l[i-1])
{
s=(1ll*s*fac[cnt])%p;
cnt=0;
}
++cnt;
}
s=(1ll*s*fac[cnt])%p;
s=(1ll*fac[n]*quickpow(s,p-2,p))%p;
ans=(1ll*ans+((1ll*s*quickpow(m,c,p))%p))%p;
return 0;
}
int search(int now,int lo,int hi)
{
if(!hi)
{
getans(now-1);
}
if(hi<lo)
{
return 0;
}
for(register int i=lo; i<=hi; ++i)
{
l[now]=i;
search(now+1,i,hi-i);
l[now]=0;
}
return 0;
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
fac[0]=1;
for(register int i=1; i<=n; ++i)
{
fac[i]=(1ll*fac[i-1]*i)%p;
}
search(1,1,n);
printf("%d\n",(int)((1ll*ans*quickpow(fac[n],p-2,p))%p));
return 0;
}