本质上是在对边求置换,然后每个循环里涂一样的颜色,但是还是要点上入手,考虑每条边的两个端点是否在一个循环里
如果在一个循环里,那么当循环长度$len$为奇数时只有转一整圈才行,而边的总数是$frac{len(len-1)}{2}$,所以有$frac{frac{len(len-1)}{2}}{len}=leftlfloorfrac{len}{2} ight floor$个循环节;当循环长度为偶数时除了上面这种情况正对的每对点旋转$frac{len}{2}$就可以,所以也是有$frac{frac{len(len-1)}{2}-frac{len}{2}}{len}+frac{frac{len}{2}}{frac{len}{2}}=leftlfloorfrac{len}{2} ight floor$个循环节
如果不在一个循环里,那循环节数量就是套路的两者所在循环长度的GCD
那么暴搜数的拆分得到每种点置换就可以求出答案了,具体的,在总共$n!$种点置换中,每个循环节$i$自己做圆排列除去$len[i]$,同时每个长度的循环节之间的排列也要除去,所以设$cnt[i]$表示长度为$i$的循环节的数量,那么满足拆分$len[1],len[2],len[3].....len[m]$的点置换的方案数就是
$frac{n!}{prodlimits_{i=1}^mlen[i]prodlimits_{i=1}^mcnt[i]!}$
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=64; 6 int n,m,p,ans,fac[N],inv[N],len[N]; 7 int GCD(int a,int b) 8 { 9 return b?GCD(b,a%b):a; 10 } 11 int Qpow(int x,int k) 12 { 13 if(k==1) return x; 14 int tmp=Qpow(x,k/2); 15 return k%2?1ll*tmp*tmp%p*x%p:1ll*tmp*tmp%p; 16 } 17 int Inv(int x) 18 { 19 return Qpow(x,p-2); 20 } 21 void Calc(int cnt) 22 { 23 int pts=1,bas=1,lst=1,sum=0; 24 for(int i=1;i<=cnt;i++) 25 { 26 bas=1ll*bas*len[i]%p; 27 if(len[i]!=len[lst]) 28 bas=1ll*bas*fac[i-lst]%p,lst=i; 29 } 30 bas=1ll*bas*fac[cnt-lst+1]%p; 31 pts=1ll*fac[n]*Inv(bas)%p; 32 for(int i=1;i<=cnt;i++) 33 { 34 sum+=len[i]/2; 35 for(int j=i+1;j<=cnt;j++) 36 sum+=GCD(len[i],len[j]); 37 } 38 ans=(ans+1ll*pts*Qpow(m,sum)%p)%p; 39 } 40 void DFS(int cnt,int mnn,int mxx) 41 { 42 if(!mxx) Calc(cnt); 43 else 44 for(int i=mnn;i<=mxx;i++) 45 len[cnt+1]=i,DFS(cnt+1,i,mxx-i); 46 } 47 int main() 48 { 49 scanf("%d%d%d",&n,&m,&p); 50 fac[0]=inv[0]=1; 51 for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%p; 52 inv[n]=Inv(fac[n]); 53 for(int i=n-1;i;i--) inv[i]=1ll*inv[i+1]*(i+1)%p; 54 DFS(0,1,n),printf("%lld",1ll*ans*inv[n]%p); 55 return 0; 56 }