题解:置换+dfs
无向完全图如果只考虑点的话,那么就是可以等价成一个点。
因为这道题的置换是点的全排列,那么我们先考虑如何求点的置换。
我们的目标就是求出每个m(f)有多少个取值,然后快速计算,m(f)跟边置换有关,暂且不说,但是数量与点置换相关,我们现在考虑每个m(f)对应的点置换的数量。m(f)是否相同应该与点的划分方式有关(就是拆成几个轮换,以及每个轮换中元素的数量)。计算的时候利用组合数和阶乘。首先如果两个组的元素个数相同,那么在选取的时候先选哪一个都一样,所以需要除去出现次数的阶乘(就是这几个组的全排列)。然后对于组内的元素有(个数-1)的阶乘种排列方式,为啥是个数-1?因为我们要求每个位置都是乱的就是不能再拆成别的轮换。
现在考虑如何计算m(f).
分成两部分考虑:
(1)两个轮换之间的边,形成的轮换的个数是gcd(x,y),其中x,y为轮换中点的数量。
(2)同一轮换中的边,轮换的个数是size/2,我也不是很清楚为什么,只能画画图感受一下(其实就是找规律。。。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 103 #define LL long long using namespace std; int n,q[N],use[N],vis[N],num[N],len,m,k[N]; LL cnt,c[N][N],ans,p,d[N],jc[N],inv[N],a[N],g[N][N],b[N*N]; LL quickpow(LL num,int x) { LL ans=1; LL base=num%p; while (x) { if (x&1) ans=ans*base%p; x>>=1; base=base*base%p; } return ans; } void init(int n) { for (int i=0;i<=n;i++) c[i][0]=1; for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%p; jc[0]=1; for (int i=1;i<=n;i++) jc[i]=jc[i-1]*i%p; for (int i=1;i<=n;i++) inv[i]=quickpow(jc[i],p-2); b[0]=1; for (int i=1;i<=n*n;i++) b[i]=b[i-1]*m%p; } int gcd(int x,int y){ if (g[x][y]) return g[x][y]; int r; while (y){ r=x%y; x=y; y=r; } return g[x][y]=x; } void dfs(int x,int now) { if (!now) { int n1=x-1; int sum=0; for (int i=1;i<=n1;i++) for (int j=i+1;j<=n1;j++) sum+=gcd(a[i],a[j]); for (int i=1;i<=n1;i++) sum+=a[i]/2; LL tot=1; int cnt=n; for (int i=1;i<=n1;i++) tot=tot*c[cnt][a[i]]%p,cnt-=a[i]; for (int i=1;i<=n;i++) if (k[i]) tot=tot*inv[k[i]]%p; for (int i=1;i<=n1;i++) tot=tot*jc[a[i]-1]%p; ans=(ans+tot*b[sum])%p; //cout<<tot<<" "<<sum<<endl; return; } for (int i=a[x-1];i<=now;i++) { k[i]++; a[x]=i; dfs(x+1,now-i); k[i]--; } } int main() { scanf("%d%d%lld",&n,&m,&p); if (n==1) { printf("1\n"); return 0; } a[0]=1; init(53); dfs(1,n); ans=ans*quickpow(jc[n],p-2)%p; printf("%lld\n",ans); }