[HNOI2009] 图的同构

1488: [HNOI2009]图的同构

Time Limit: 10 Sec  Memory Limit: 64 MB
Submit: 700  Solved: 470
[Submit][Status][Discuss]

Description

求两两互不同构的含n个点的简单图有多少种。

简单图是关联一对顶点的无向边不多于一条的不含自环的图。

a图与b图被认为是同构的是指a图的顶点经过一定的重新标号以后,a图的顶点集和边集能完全与b图一一对应。
 

 

Input

输入一行一个整数N,表示图的顶点数,0<=N<=60

Output

输出一行一个整数表示含N个点的图在同构意义下互不同构的图的数目,答案对997取模。

Sample Input

输入1
1

输入2
2

输入3
3

Sample Output

输出1
1

输出2
2

输出3
4
 
 
    很经典的一类等价图计数问题。。。。
    可以先等价于每条边可以染两种颜色,求两两本质不同的图的个数。。。
 
    最朴素的想法就是找出所有边的置换,对每个置换求一下环数,然后用p????引理算一下方案数,最后除以总置换数(也就是边数的阶乘的逆元)就可以得到答案了。。。
    但是有一些边的置换是会出现矛盾的。。。因为边置换要求两个顶点映射到新边的两个顶点上,难免会产生冲突。
    于是我们换一种思路,枚举点的置换,边的置换就随之确定了,并且会不重不漏的算上所有边的置换(想一想为什么)
 
    现在的问题就变成了:给你一个点的置换,问你边的置换的环数。
    先把点置换表示成若干个环的乘积,然后同一个环内部点之间的边的置换环数为 (点的)环大小/2,连接不同环之间的所有边的置换环数为 gcd(siz1,siz2)。
    理解这一步非常的关键。
   
    首先不妨将(点的)环上乱序的数写成{1,2,....,siz},这样并不影响最后的(边的)环数。
    这样 (1,i) 这条边是和任意 ( j , (j+i-1)%siz +1)的边等价的,而且当i>n/2的时候显然不合法,因为(i,1)这条边之前已经在别的等价类里被算过了。
    于是我们就求出了第一种边的置换环数。。。
 
    第二种的话,由于每转一圈,一个环中的点对应的另一个环中的点会产生 |siz1 - siz2|的偏位移,由同余相关定理可以很轻松的算出每个环的大小都是 siz1 * siz2 / gcd(siz1 , siz2),所以环数就是 gcd(siz1 , siz2)了。
 
    于是就可以A题了???
 
    并不,枚举排列的时候就已经超时了2333
    不过观察上述做法可以发现,一种置换的方案数之和 (点的)环的大小(可重)集合有关。
    所以我们改用??数(我也忘了叫啥了。。反正不大)的复杂度枚举一下可重集中的元素是哪些,算出了这个可重集的方案数之后再乘上可以映射到这个可重集上的排列数 加到答案里就好啦。。。。
 
    至于映射数?
    (组合数学入门问题,自己推推吧2333)
 
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int ha=997,mod=ha-1;

inline int add(int x,int y){ x+=y; return x>=ha?x-ha:x;}
inline void ADD(int &x,int y){ x+=y; if(x>=ha) x-=ha;}

inline int ksm(int x,int y){
	int an=1;
	for(;y;y>>=1,x=x*(ll)x%ha) if(y&1) an=an*(ll)x%ha;
	return an;
}

int gcd[73][73],n,ci[1005],ans,jc[73],ni[73];
int m,a[65],tot,now,C[73][73],b[65];

inline void calc(){	
    m=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=b[i];j++) a[++m]=i;

	tot=0,now=1;
	for(int i=1;i<=m;i++) tot+=a[i]>>1;
	for(int i=1;i<=m;i++)
	    for(int j=i+1;j<=m;j++) tot+=gcd[a[i]][a[j]];
	
	if(tot>=mod) tot%=mod;
	
	for(int T=n,i=1;i<=m;T-=a[i],i++) now=now*(ll)C[T][a[i]]%ha*(ll)jc[a[i]-1]%ha;
	for(int i=1;i<=n;i++) if(b[i]>1) now=now*(ll)ni[b[i]]%ha;
	
	ADD(ans,now*(ll)ci[tot]%ha);
}

void dfs(int x,int lef){
	if(lef<x){ if(!lef) calc(); return;}
	for(int i=0,u=0;u<=lef;i++,u+=x) b[x]=i,dfs(x+1,lef-u),b[x]=0;
}

int main(){	
	ci[0]=1; for(int i=1;i<ha;i++) ci[i]=add(ci[i-1],ci[i-1]);
	jc[0]=1; for(int i=1;i<=60;i++) jc[i]=jc[i-1]*(ll)i%ha;
	ni[60]=ksm(jc[60],ha-2);
	for(int i=60;i;i--) ni[i-1]=ni[i]*(ll)i%ha;	
	
	C[0][0]=1;
	for(int i=1;i<=60;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=add(C[i-1][j-1],C[i-1][j]);
	}
	
	scanf("%d",&n);
	
	for(int i=0;i<=n;i++)
	    for(int j=0;j<=i;j++)
	        if(!i||!j) gcd[i][j]=i+j;
	        else gcd[j][i]=gcd[i][j]=gcd[j][i%j];
	
    dfs(1,n),ans=ans*(ll)ni[n]%ha;
	
	printf("%d\n",ans);
	return 0;
} 

  

posted @ 2018-06-29 19:25  蒟蒻JHY  阅读(788)  评论(0编辑  收藏  举报