[HNOI2009] 图的同构计数

因为要求本质不同的图,容易想到群论。

为了方便处理,将边是否存在转化为边的黑白染色问题(实际上就是 \([SHOI2006]\) 有色图 的弱化版本,最终公式也差不多)。

根据 \(Burnside\) 引理和 \(Polya\) 定理,将问题转化为:对于每种置换方案,有多少个边的等价类。

考虑对于一种置换方案 \(g\),设它有 \(k_g\) 个循环节,每个循环节长度为 \(a_{g,i}\)

现在我们将问题分为两个部分:循环节内的等价类数量和循环节间的等价类数量。

对于循环节内部,容易想到将其转化为一个正 \(a_{g,i}\) 边形,其中长度相同的边在同一等价类中,所以循环节内部等价类数量就是 \(\sum\lfloor \dfrac{a_{g,i}}{2}\rfloor\)

对于循环节间,为了让其对齐,所以要进行 \(\text{lcm}(a_{g,i},a_{g,j})\) 次轮换才能对齐一次,所以等价类个数为 \(\dfrac{a_{g,i}\times a_{g,j}}{\text{lcm}(a_{g,i},a_{g,j})}=\gcd(a_{g,i},a_{g,j})\)

那么对于一个置换 \(g\),它的等价类总数就是 \(f'(g)=\sum\limits_{i=1}^{k_g}\lfloor\dfrac{a_{g,i}}{2}\rfloor+\sum\limits_{i=1}^{k_g-1}\sum\limits_{j=i+1}^{k_g}\gcd(a_{g,i},a_{g,j})\)

根据 \(Burnside\) 引理,答案就是 \(\dfrac{1}{|G|}\sum\limits_{g\in G}2^{f'(g)}\)

但是由于 \(|G|=n!\),所以仍然无法通过。考虑枚举 \(a_g\) 数组。

\(f(a)=\sum\limits_{i=1}^{k}\lfloor \dfrac{a_i}{2}\rfloor+\sum\limits_{i=1}^{k-1}\sum\limits_{j=i+1}^k\gcd(a_i,a_j)\)\(num(a)\) 表示循环节情况为 \(a\) 的置换的个数,则答案可以改写为 \(\dfrac{1}{|G|}\sum\limits_a2^{f(a)}num(a)\)

这里 \(dfs\) 拆分 \(n\) 为多个数的和时间复杂度不会证,但是应该没有问题,以后再补这个锅吧。

那么问题来到了如何求解 \(num(a)\) 上。

首先将 \(n\) 个数分到 \(a_i\) 里,方案数为 \(\dfrac{n!}{\prod_{i=1}^ka_i!}\),乘上圆排列的方案数 \(\prod_{i=1}^k(a_i-1)!\),得方案数为 \(\dfrac{n!}{\prod_{i=1}^ka_i}\)

但这样并不正确,因为相同长度的循环节可以互相交换,导致重复。因此设 \(b_i=\sum\limits_{j=1}^k[a_j=i]\),于是就可以得到最终形态:

\[num(a)=\dfrac{n!}{\prod\limits_{i=1}^ka_i\prod\limits_{i=1}^nb_i!} \]

\(num(a)\) 带入原式得:

\[ans=\sum\limits_a2^{f(a)}(\prod\limits_{i=1}^ka_i\prod\limits_{i=1}^nb_i!)^{-1} \]

\[f(a)=\sum\limits_{i=1}^{k}\lfloor \dfrac{a_i}{2}\rfloor+\sum\limits_{i=1}^{k-1}\sum\limits_{j=i+1}^k\gcd(a_i,a_j) \]

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=65,p=997;
int n,ans,a[N],b[N],jc[N],gc[N][N];
int gcd(int x,int y){
	return !y?x:gcd(y,x%y);
}int qpow(int x,int y){
	int re=1;
	while(y){
		if(y&1) re=re*x%p;
		x=x*x%p,y>>=1;
	}return re;
}void check(int k){
	int f=0,prd=1;
	for(int i=1;i<=k;i++){
		f+=a[i]/2;
		for(int j=i+1;j<=k;j++)
			f+=gc[a[i]][a[j]];
	}for(int i=1;i<=k;i++)
		prd=prd*a[i]%p;
	for(int i=1;i<=n;i++)
		prd=prd*jc[b[i]]%p;
	ans=(ans+qpow(2,f)*qpow(prd,p-2))%p;
}void dfs(int x,int num,int lst){
	if(!num) return check(x-1),void();
	for(int i=lst;i<=num;i++)
		b[i]++,a[x]=i,dfs(x+1,num-i,i),b[i]--;
}signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n,jc[0]=1;
	for(int i=1;i<=n;i++)
		jc[i]=jc[i-1]*i%p;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			gc[i][j]=gcd(i,j);
	dfs(1,n,1),cout<<ans; 
	return 0;
}
posted @   长安一片月_22  阅读(7)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示