Luogu P4727-- 【HNOI2009】圖的同構
Luogu P4727-- 【HNOI2009】圖的同構
Description
求兩兩互不同構的含n個點的簡單圖有多少種。
簡單圖是關聯一對頂點的無向邊不多於一條的不含自環的圖。
a圖與b圖被認為是同構的是指a圖的頂點經過一定的重新標號以後,a圖的頂點集和邊集能完全與b圖一一對應。
Input
輸入一行一個整數N,表示圖的頂點數,0<=N<=60
Output
輸出一行一個整數表示含N個點的圖在同構意義下互不同構的圖的數目,答案對997取模。
根據Burnside引理,答案就是所有置換下不動元的平均數(人話)。一個置換可以理解為一種排列,所以置換總數就是\(n!\)。
然後考慮求出所有的不動元。
首先,假設我們已近得到了\(m\)個迴圈節,他們的長度分別為\(L_1,L_2...L_m\)\((L_1+L_2+...+L_m=n)\)。
然後連邊的時候我們分兩種情況:
- 邊的兩端在同一迴圈節內。
- 邊的兩端不在同一迴圈節內。
情況1:
我們設有迴圈節的大小為\(n\)。則有\(2^{\lfloor \frac{n}{2}\rfloor}\)種。
這個可以畫圖感受一下:我們先把點按\(1\)到\(n\)重標號,假設有邊\((1,k)\),那麼一定有邊\((2,2+k-1),(3,3+k-1)...(n,k-1)\)(因為)。然後\(k>2^{\lfloor \frac{n}{2}\rfloor}\)
情況2:
我們設兩個迴圈節大小分別為\(n,m\)。則有\(2^{gcd(n,m)}\)種。
我們又來感受一下:我們假設存在邊\((1,i)\),則必有邊\((2,i+1),(3,i+2)...\)如果想讓這個迴圈回到\((1,i)\),那麼至少要有\(lcm(n,m)\)條邊,所以等價類有\(\frac{nm}{lcm(n,m)}=gcd(n,m)\)種。
然後我們得到了迴圈節為\((L_1,L_2...L_m)\)的圖的情況數,我們還需要得到迴圈節為\((L_1,L_2...L_m)\)
種數是\(\frac{n!}{\Pi B_i \Pi Li}\)(其中\(B_i\)表示長度為\(i\)的迴圈節數量)。
首先我們不妨設\((L_1\leq L_2\leq...\leq L_m)\)。然後我們按照\(1\)到\(m\)的順序將得到的一個\(1\)到\(n\)的排列劃分。這樣就有\(n!\)個序列。
長度相同的迴圈節的排列會算重:比如\((1,2)(3,4)\)與\((3,4)(1,2)\)應該算作一種(這裡\((1,2)\)僅僅表示\(1,2\)在一個迴圈中)。所以要除以\(\Pi B_i\)。
同一迴圈內也會算重:比如\((1,2,3)\)與\((2,3,1)\)算作一種(這裡的\((1,2,3)\)表示的是\((\begin{matrix} 1 2 3\\ 2 3 1\end{matrix})\))。所以要除以\(\Pi Li\)。
然後就完了。
程式碼:
#include<bits/stdc++.h>
#define ll int
#define mod 997
#define N 65
using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
int n;
int ksm(int t,int x) {
ll ans=1;
for(;x;x>>=1,t=t*t%mod)
if(x&1) ans=ans*t%mod;
return ans;
}
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int g[N][N];
int f[N],fac[N],inv[mod+5];
int ans;
int num[N];
int cnt;
int pw2[100005];
void dfs(int v,int res) {
if(!res) {
int now=fac[n];
int mi=0;
for(int i=v+1;i<=n;i++) {
now=now*inv[fac[num[i]]*ksm(i,num[i])%mod]%mod;
mi+=i/2*num[i];
for(int j=i+1;j<=n;j++) {
mi+=g[i][j]*num[i]*num[j];
}
mi+=i*num[i]*(num[i]-1)/2;
}
now=now*pw2[mi%(mod-1)]%mod;
(ans+=now)%=mod;
return ;
}
if(!v) return ;
num[v]=0;
dfs(v-1,res);
for(int i=1;i*v<=res;i++) {
num[v]=i;
dfs(v-1,res-i*v);
num[v]=0;
}
}
int main() {
n=Get();
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
pw2[0]=1;
for(int i=1;i<=100000;i++) pw2[i]=(pw2[i-1]<<1)%mod;
inv[0]=inv[1]=1;
for(int i=2;i<=mod;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=n;i++) {
for(int j=i;j<=n;j++) {
g[i][j]=gcd(i,j);
}
}
dfs(n,n);
cout<<ans*ksm(fac[n],mod-2)%mod;
return 0;
}