HNOI 2009 图的同构记数 题解

题目传送门

题目大意: 问有多少个不同构的 n n n 个点的图。

题解

A图的顶点经过一定的重新标号以后,A图的顶点集和边集要完全与B图一一对应。这是题目里告诉我们的同构的条件,这应该算半个提示了,我们可以认为,重新标号是一种置换,这种置换形如:
( 1 2 . . . n − 1 n a 1 a 2 . . . a n − 1 a n ) \left( \begin{matrix} 1 & 2 & ... & n-1 & n\\ a_1 & a_2 & ... & a_{n-1} & a_n \end{matrix} \right) (1a12a2......n1an1nan)

那么总的置换数量就是 ∣ G ∣ = n ! |G|=n! G=n!,然后用burnside定理来求,也就是要求出每种置换有多少个不动点。

先将一个置换表示成若干个循环的乘积,然后考虑连边,假如循环 A A A 内第 x x x 个元素和第 y y y 个元素之间连了边,因为要在置换后图保持不变,所以第 x + 1 x+1 x+1 和第 y + 1 y+1 y+1 个元素之间也要有连边,同理,距离为 ∣ x − y ∣ |x-y| xy 的元素之间都需要连边。

可以发现,对于一个长度 d d d,要么全部连边,要么全都不连,而 d d d 的取值范围是 [ 1 , ⌊ L i 2 ⌋ ] [1,\lfloor \frac {L_i} 2 \rfloor] [1,2Li],其中 L A L_A LA 是这个循环的长度,则一共有 2 ⌊ L i 2 ⌋ 2^{\lfloor \frac {L_i} 2 \rfloor} 22Li 种连边方案。

再考虑两个不同的循环 A A A B B B,类似的,假如 A A A 中的第 i i i 个元素和 B B B 中的第 j j j 个元素之间连了边,那么 i + 1 i+1 i+1 j + 1 j+1 j+1 之间也要连,以此类推下去要连 l c m ( L A , L B ) lcm(L_A,L_B) lcm(LA,LB) 条边,不妨称这样的 ( i , j ) , ( i + 1 , j + 1 ) (i,j),(i+1,j+1) (i,j),(i+1,j+1) 为同一组边。

因为一共有 L A × L B L_A\times L_B LA×LB 条边,所以一共有 L A × L B l c m ( , L A ) , L B = gcd ⁡ ( L A , L B ) \frac {L_A\times L_B} {lcm(,L_A),L_B}=\gcd(L_A,L_B) lcm(,LA),LBLA×LB=gcd(LA,LB) 组边,每组边也是要么连要么不连,方案数为 2 gcd ⁡ ( L A , L B ) 2^{\gcd(L_A,L_B)} 2gcd(LA,LB)

所以答案就是:
1 ∣ G ∣ ∑ L 1 , L 2 , . . . , L k ( ∏ i = 1 k 2 ⌊ L i 2 ⌋ × ∏ 1 ≤ A < B ≤ k 2 gcd ⁡ ( L A , L B ) ) \frac 1 {|G|}\sum_{L_1,L_2,...,L_k}\left(\prod_{i=1}^k2^{\lfloor \frac {L_i} 2 \rfloor}\times \prod_{1\leq A<B\leq k} 2^{\gcd(L_A,L_B)}\right) G1L1,L2,...,Lk(i=1k22Li×1A<Bk2gcd(LA,LB))

但是这样的话时间复杂度为 O ( n ! × n 2 ) O(n!\times n^2) O(n!×n2),可以愉快地得到 T L E TLE TLE 的好成绩。

考虑优化,观察后可以发现,我们并不关心每个循环内的点是谁,只关心这个置换能拆成几个循环以及每个循环的大小 L i L_i Li

于是考虑直接枚举 L i L_i Li,相当于将 n n n 进行自然数拆分,经暴力发现,当 n = 60 n=60 n=60 时,拆分方案大概只有 1 0 6 10^6 106 种,可以接受。

当我们枚举出所有 L i L_i Li 之后,就可以用上面的柿子求解,但是还需要求多一个东西:有多少个置换拆成循环之后,循环的长度分别是 L 1 , L 2 , . . . , L k L_1,L_2,...,L_k L1,L2,...,Lk

这个很简单,排列组合一下就能得到:

先给这 k k k 个循环安排元素进去,方案数为 n ! ∏ i = 1 k L i ! \dfrac {n!} {\prod_{i=1}^k L_i!} i=1kLi!n!

再考虑每个循环内的元素顺序,不考虑重复的话就有 L i ! L_i! Li! 排列,但是对于一个循环而言, ( a 1 a 2 a 3 . . . a n ) (a_1a_2a_3...a_n) (a1a2a3...an) ( a 2 a 3 a 4 . . . a n a 1 ) (a_2a_3a_4...a_na_1) (a2a3a4...ana1) 其实是同样的排列,所以要除以 L i L_i Li,即方案数为 L i ! L i = ( L i − 1 ) ! \dfrac {L_i!} {L_i}=(L_i-1)! LiLi!=(Li1)!

然后对于长度相同的两个循环,比如说 ( 1   2 ) ( 3   4 ) (1~2)(3~4) (1 2)(3 4),它其实等价于 ( 3   4 ) ( 1   2 ) (3~4)(1~2) (3 4)(1 2),设长度为 i i i 的循环有 S i S_i Si 个,那么还需要除以 ∏ i = 1 k S i ! \prod_{i=1}^k S_i! i=1kSi!

所以一共有 n ! ∏ i = 1 k L i × ∏ i = 1 k S i ! \frac {n!} {\prod_{i=1}^k L_i\times\prod_{i=1}^k S_i!} i=1kLi×i=1kSi!n! 个置换满足要求。

枚举出 L L L 之后,把这个方案数和上面那个柿子乘起来就是答案了。以及,这里的 n ! n! n! 和上面的 1 ∣ G ∣ \frac 1 {|G|} G1 可以抵消掉。

设对 n n n 进行自然数拆分的时间复杂度为 K K K,那么最后的时间复杂度为 O ( K × n 2 ) O(K\times n^2) O(K×n2)

讲道理,当 n = 60 n=60 n=60 的时候时间复杂度大概为 3.6 × 1 0 9 3.6\times 10^9 3.6×109,虽然说完全跑不满,但是实测也需要 3 s 3s 3s 左右,为什么能过呢……

太烧脑了果然还是不管好了qwq,代码如下:

#include <cstdio>
#define mod 997
#define maxn 110

int n;
int ksm(int x,int y)
{
	int re=1;
	while(y)
	{
		if(y&1)re=1ll*re*x%mod;
		x=1ll*x*x%mod;y>>=1;
	}
	return re;
}
int fac[maxn],inv_fac[maxn],inv[maxn],gcd[maxn][maxn];
int gcd_(int x,int y){return y==0?x:gcd_(y,x%y);}
void work()
{
	fac[0]=inv_fac[0]=inv[1]=1;
	for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv_fac[n]=ksm(fac[n],mod-2);
	for(int i=n-1;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
	for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)gcd[i][j]=gcd_(i,j);
}
int L[maxn],S[maxn],ans=0;
void work(int k)
{
	int prod=1;
	for(int i=1;i<=k;i++)prod=1ll*prod*((1ll<<(L[i]/2))%mod)%mod;
	for(int i=1;i<=k;i++)for(int j=i+1;j<=k;j++)prod=1ll*prod*(1ll<<gcd[L[i]][L[j]])%mod;
	for(int i=1;i<=k;i++)prod=1ll*prod*inv[L[i]]%mod;
	for(int i=1;i<=n;i++)prod=1ll*prod*inv_fac[S[i]]%mod;
	ans=(ans+prod)%mod;
}
int min(int x,int y){return x<y?x:y;}
void dfs(int x,int k)
{
	if(!x)return (void)work(k-1);
	for(int i=min(L[k-1],x);i>=1;i--)
	L[k]=i,S[i]++,dfs(x-i,k+1),S[i]--;
}

int main()
{
	scanf("%d",&n);work();
	L[0]=n;dfs(n,1);
	printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值