bzoj 1488: [HNOI2009]图的同构

Description

求两两互不同构的含n个点的简单图有多少种。
简单图是关联一对顶点的无向边不多于一条的不含自环的图。
a图与b图被认为是同构的是指a图的顶点经过一定的重新标号以后,a图的顶点集和边集能完全与b图一一对应。

Solution

转化模型:给边 (0/1) 染色,如果为 (1) 则代表选择,求方案数
考虑一下这个题的置换实际上是边置换,而把边置换用到的节点集合拿出来,发现这些点集合也可以是点置换
于是我们想到用把边置换按照点置换归类

于是复杂度就从边数降到点数了,枚举点置换的复杂度实际上是求 (n) 的划分的复杂度,(n=60) 时,约为 (10^6) 级别
问题在于如何统计点置换中的边循环个数

利用 (polya) 公式:
(L=frac{1}{|G|}*sum_{i∈ G} 2^k_i)
(k_i) 是每一个置换的循环的个数

考虑边的两端点都在同一点置换的边置换个数
这个置换的要求是:经过每一个点至少一次且形成一个环
观察一个例子
题面
我们可以走相邻的点(也就是红边)
或者隔两个点走一步(绿边)
然后发现隔三个点走的走出来的和绿边一模一样,于是算重了
于是总结出规律: 隔 (i)(n-i) 个点走出来的边置换是一样的,所以边置换个数就是 (frac{n}{2})

再考虑两端点都不在同以点置换的边置换个数
我们反复横跳两个点置换,直到某个时刻都遍历完,循环的大小为 (lcm(a,b)),(a,b) 为两个循环的大小
因为总边数是 (a*b) ,所以循环个数就为 (frac{a*b}{lcm(a,b)}=gcd(a,b))

最后就只要统计点置换的个数了
容易发现就是: (frac{n!}{size[1]*size[2]*...*size[n]*t[1]!*t[2]!*...*t[n]!})
(size) 表示每一个点置换的大小, (t[i]) 表示大小为 (i) 的点置换的个数
因为点是一个环,所以多枚举了 (size) 次,另外对于大小为 (i) 的连通块出现了多次,相当于一个可重排列,除以 (t[1]!)

最后总置换个数是 (n!),要记得除

#include<bits/stdc++.h>
using namespace std;
const int mod=997,N=65;
int n,num=0,sz[N],w[N],Fac[N],ans=0;
inline int qm(int x,int k){
    int sum=1;
    while(k){
        if(k&1)sum=1ll*sum*x%mod;
        x=1ll*x*x%mod;k>>=1;
    }
    return sum;
}
inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline void dfs(int B,int res){
    if(!res){
        int sam=1,tot=0;
        for(int i=1;i<=num;i++){
            tot+=sz[i]*(sz[i]-1)/2*w[i]+w[i]/2*sz[i];
            for(int j=i+1;j<=num;j++)
                tot+=sz[i]*sz[j]*gcd(w[i],w[j]);
        }
        for(int i=1;i<=num;i++)
            sam=sam*Fac[sz[i]]*qm(w[i],sz[i])%mod;
        sam=Fac[n]*qm(sam,mod-2)%mod;
        ans=(ans+qm(2,tot)*sam)%mod;
        return ;
    }
    if(B==n+1 || B>res)return ;
    dfs(B+1,res);
    for(int i=1;i*B<=res;i++){
        w[++num]=B;sz[num]=i;
        dfs(B+1,res-i*B);
        num--;
    }
}
int main(){
  scanf("%d",&n);
  Fac[0]=1;for(int i=1;i<=n;i++)Fac[i]=Fac[i-1]*i%mod;
  dfs(1,n);
  ans=ans*qm(Fac[n],mod-2)%mod;
  printf("%d
",ans);
  return 0;
}
原文地址:https://www.cnblogs.com/Yuzao/p/8486474.html