problem
求出含 n n n 个点的两两不同构的图的数量。
其中 A \texttt A A 与 B \texttt B B 被认为是同构的是指: A \texttt A A 图的顶点经过一定的重新标号以后, A \texttt A A 图的顶点集和边集要完全与 B \texttt B B 图一一对应。
数据范围: 0 ≤ n ≤ 60 0≤n≤60 0≤n≤60。
solution
首先,对于边的存在与否问题,我们可以看成对边黑白染色,这提示我们用 Polya 引理。
边的置换貌似不好找,不妨枚举点的置换,想办法在一个对于点的置换中找到边的不动点。
对于某条边 ( u , v ) (u,v) (u,v),它可能有两种情况(设边的长度为两端点在环上经过边的数量):
- u , v u,v u,v 在同一个循环中,且循环的大小为 A A A。显然长度相等的边在同一个等价类中,又由于这是环,长度为 l l l 和 A − l + 1 A-l+1 A−l+1 的边也是等价的,所以有 ⌊ A 2 ⌋ \lfloor\frac A 2\rfloor ⌊2A⌋ 个等价类。
- u , v u,v u,v 在不同的循环中,且循环的大小为 A , B A,B A,B。 u u u 在 A A A 上跑, v v v 在 B B B 上跑,一共可以遍历到 l c m ( A , B ) \mathrm{lcm}(A,B) lcm(A,B) 条边,由于总共有 A B AB AB 条边,所以边有 A B l c m ( A , B ) = gcd ( A , B ) \frac{AB}{\mathrm{lcm}(A,B)}=\gcd(A,B) lcm(A,B)AB=gcd(A,B) 个等价类。
所以我们算出总的等价类数目 s u m sum sum,当前置换的贡献就是 2 s u m 2^{sum} 2sum。
但是直接 O ( n ! ) O(n!) O(n!) 枚举点的置换显然是不行的,由于我们只关心所有循环的长度,可以暴力正整数拆分,然后考虑有多少个置换满足这个正整数拆分即可。
具体来说,设 c i c_i ci 表示长度为 i i i 的循环的个数,那么置换个数即为:
n ! ∏ c i ! × i c i \frac{n!}{\prod c_i!\times i^{c_i}} ∏ci!×icin!
意思就是,对于大小相等的环随便排都可以,所以除掉 c i ! c_i! ci!;对于环内的每个点也是不考虑顺序的,还要除 i c i i^{c_i} ici。
其实也就是可重集的排列。
code
#include<bits/stdc++.h>
using namespace std;
const int N=65,P=997;
int n,ans,inv[N],fac[N],ifac[N],Gcd[N][N],num[N],a[N];
int add(int x,int y) {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y) {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y) {return x*y>=P?x*y%P:x*y;}
int power(int a,int b){
int ans=1;
for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a);
return ans;
}
void init(){
fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=1;
for(int i=2;i<=n;++i) fac[i]=mul(fac[i-1],i);
for(int i=2;i<=n;++i) inv[i]=mul(P-P/i,inv[P-P/i*i]);
for(int i=1;i<=n;++i) ifac[i]=mul(ifac[i-1],inv[i]);
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) Gcd[i][j]=__gcd(i,j);
}
void calc(){
int res=fac[n],cnt=0;
for(int i=1;i<=n;++i){
res=mul(res,ifac[num[i]]);
for(int j=1;j<=num[i];++j)
a[++cnt]=i,res=mul(res,inv[i]);
}
int val=0;
for(int i=1;i<=cnt;++i) val+=a[i]/2;
for(int i=1;i<=cnt;++i) for(int j=i+1;j<=cnt;++j) val+=Gcd[a[i]][a[j]];
ans=add(ans,mul(res,power(2,val)));
}
void dfs(int x,int sum){
if(x==1) {num[x]=n-sum,calc();return;}
for(int i=0;sum+i*x<=n;++i) num[x]=i,dfs(x-1,sum+i*x);
}
int main(){
scanf("%d",&n),init(),dfs(n,0);
printf("%d\n",mul(ans,ifac[n]));
return 0;
}