首先可以把问题转化成在完全图上对边进行黑白染色。
对于每个点的置换,要求出有多少关于边的不动点。把置换分解成循环,可以发现,一个长度为x的点的循环内部有
但是直接枚举点的置换有
(nl1)(n−l1l2)⋯(n−l1−⋯−lm−1lm)=n!∏li!
然后,固定第一个元素,每个循环有(li−1)!种排法。同时,相同长度的循环彼此之间的顺序被重复考虑。所以最后的答案是
n!∏li∏ci
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int p=997;
int n,ans,fac[65],inv[65],invfac[65],cnt[65],a[65],gcd[65][65],node;
int pow(int base,int k)
{
int ret=1;
for (;k;k>>=1,base=base*base%p)
if (k&1) ret=ret*base%p;
return ret;
}
int getgcd(int x,int y)
{
if (gcd[x][y]) return gcd[x][y];
return gcd[x][y]=y?getgcd(y,x%y):x;
}
void check()
{
int ret=fac[n],tot=0,sum=0;
for (int i=1;i<=n;i++)
{
ret=ret*invfac[cnt[i]]%p;
for (int j=1;j<=cnt[i];j++)
a[++tot]=i,ret=ret*inv[i]%p;
}
for (int i=1;i<=tot;i++)
{
sum+=a[i]/2;
for (int j=i+1;j<=tot;j++) sum+=gcd[a[i]][a[j]];
}
ret=ret*pow(2,sum)%p;
ans=(ans+ret)%p;
}
void dfs(int now,int num)
{
//node++;
if (now==1)
{
cnt[1]=n-num;
check();
return;
}
for (int i=0;num+i*now<=n;i++)
{
cnt[now]=i;
dfs(now-1,num+i*now);
}
}
int main()
{
scanf("%d",&n);
fac[0]=invfac[0]=1;
for (int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%p;
inv[i]=pow(i,p-2);
invfac[i]=pow(fac[i],p-2);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
gcd[i][j]=getgcd(i,j);
dfs(n,0);
//printf("%d\n",node);
printf("%d\n",ans*invfac[n]%p);
}