1. 程式人生 > >Luogu P4727-- 【HNOI2009】圖的同構

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. 邊的兩端在同一迴圈節內。
  2. 邊的兩端不在同一迴圈節內。
情況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}\)

情況一定與\(k-2^{\lfloor \frac{n}{2}\rfloor}\)等價。所以等價類\(\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)\)

\(n\)階置換有多少種。

種數是\(\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;
}