题目链接
http://acm.sgu.ru/problem.php?contest=0&problem=282
思路
显然有
N!
N
!
个边的置换,枚举边的置换肯定不可行。
因此,我们来考虑点置换和边置换的关系。
首先,考虑一条边
(x,y)
(
x
,
y
)
。
如果
x
x
和在点置换中的同一循环节中,设这个循环节长度为
L
L
。
若为奇数,则
(x,y)
(
x
,
y
)
所处边置换中循环节长度为
L
L
,一共有条边,所以循环节的个数为
C2LL=L−12
C
L
2
L
=
L
−
1
2
个。
若
L
L
为偶数,则所处边置换,出来上述长度为
L
L
的情况,还有一种情况:
如果在循环节中正好相距
L2
L
2
,那么循环节长度为
L2
L
2
这样的循环节一共有
L2
L
2
个,循环节的个数为
C2L−L2L+1=L2
C
L
2
−
L
2
L
+
1
=
L
2
个循环节。
再来考虑
x,y
x
,
y
不在一个循环节的情况,设循环节长度分别为
L1,L2
L
1
,
L
2
。
显然,
(x,y)
(
x
,
y
)
所在置换的循环节长度为
lcm(L1,L2)
l
c
m
(
L
1
,
L
2
)
,这样的循环节个数为
L1L2lcm(L1,L2)=gcd(L1,L2)
L
1
L
2
l
c
m
(
L
1
,
L
2
)
=
g
c
d
(
L
1
,
L
2
)
个。
若一个点置换的循环节长度分别为 L1,L2,⋯,LK L 1 , L 2 , ⋯ , L K ,那么边置换的循环节长度为:
接下来我们求满足循环节长度为
L1,L2,⋯,LK
L
1
,
L
2
,
⋯
,
L
K
的边置换个数。
这就相当于将
1⋯N
1
⋯
N
放入大小为
L1,L2,⋯,LK
L
1
,
L
2
,
⋯
,
L
K
的环中,求总方案数。
显然,如
L
L
不重复,那么总方案数为,这里不给出证明了。
若
L
L
有重复,假设中有
t
t
种不同的值,第种值的个数为
ki
k
i
,那么总方案数(也就是满足条件的边置换个数)为:
最终的答案为:
代码
#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;
}