「HNOI 2009」图的同构记数

传送门


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 0n60


solution

首先,对于边的存在与否问题,我们可以看成对边黑白染色,这提示我们用 Polya 引理。

边的置换貌似不好找,不妨枚举点的置换,想办法在一个对于点的置换中找到边的不动点

对于某条边 ( u , v ) (u,v) (u,v),它可能有两种情况(设边的长度为两端点在环上经过边的数量):

  1. u , v u,v u,v 在同一个循环中,且循环的大小为 A A A。显然长度相等的边在同一个等价类中,又由于这是环,长度为 l l l A − l + 1 A-l+1 Al+1 的边也是等价的,所以有 ⌊ A 2 ⌋ \lfloor\frac A 2\rfloor 2A 个等价类。
  2. 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值