首先,这道题可以看做将边用两种颜色(存在和不存在)染色…
然后我就想起了我的远古文章,想当年,我还是个对Polya定理一知半解的小蒟蒻,时过境迁,如今我已经变成了一个完全不知道Polya定理是什么东西的大蒟蒻了。
具体的可以去看那篇文章,我梳理一下这个模型。
- 两端点都在大小为 L L L的同一个点循环节中的边循环节数量为 ⌊ L 2 ⌋ \lfloor \frac{L}{2} \rfloor ⌊2L⌋,在大小为 L 1 L_1 L1和 L 2 L_2 L2的两个点循环节中的边循环节数量为 g c d ( L 1 , L 2 ) gcd(L_1,L_2) gcd(L1,L2)
- 暴力枚举点被拆分成哪些大小的循环节。
- 考虑将 n n n个不同编号的点插入这些大小的循环节中,考虑固定循环节表示法的第一个点后剩下的点任意排列都是不同的置换,考虑大小相同的循环节的贡献会算重,总之这个循环节拆分可以对应的置换个数是 n ! L 1 L 2 . . . L m k 1 ! k 2 ! . . . k t ! \frac{n!}{L_1 L_2 ... L_m k_1!k_2!...k_t!} L1L2...Lmk1!k2!...kt!n!( k k k代表某一个大小的循环节数量)
- 使用Polya定理 1 ∣ G ∣ ∑ k c i \frac{1}{|G|}\sum k^{c_i} ∣G∣1∑kci得到答案( c i c_i ci表示置换 i i i的循环节数量)。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=997;
int n,ans,gcd[62][62],fac[62],inv[62],ni[62],L[62],K[62],bin[3605];
int GCD(int x,int y) {return y?GCD(y,x%y):x;}
int qm(int x) {return x>=mod?x-mod:x;}
void work(int tot) {
for(RI i=1;i<=n;++i) K[i]=0;
for(RI i=1;i<=tot;++i) ++K[L[i]];
int re=fac[n],js=0;
for(RI i=1;i<=n;++i) re=re*ni[K[i]]%mod;
for(RI i=1;i<=tot;++i) re=re*inv[L[i]]%mod;
for(RI i=1;i<=tot;++i) {
js+=L[i]/2;
for(RI j=i+1;j<=tot;++j) js+=gcd[L[j]][L[i]];
}
ans=qm(ans+re*bin[js]%mod);
}
void dfs(int x,int tot,int las) {
if(x==n) {work(tot);return;}
for(RI i=1;i<=las&&x+i<=n;++i) L[tot+1]=i,dfs(x+i,tot+1,i);
}
int main()
{
scanf("%d",&n);
for(RI i=1;i<=n;++i)
for(RI j=i;j<=n;++j) gcd[i][j]=GCD(i,j);
fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod;
bin[0]=1;for(RI i=1;i<=n*n;++i) bin[i]=qm(bin[i-1]+bin[i-1]);
inv[0]=inv[1]=ni[0]=ni[1]=1;
for(RI i=2;i<=n;++i)
inv[i]=(mod-mod/i)*inv[mod%i]%mod,ni[i]=ni[i-1]*inv[i]%mod;
dfs(0,0,n),ans=ans*ni[n]%mod;
printf("%d\n",ans);
return 0;
}