【HNOI2009】图的同构(Polya)

传送门


题解:

目前是洛谷上面非打表代码的rk1。

很显然我们考虑点的 n ! n! n!种置换。

显然对应着边的 n ! n! n!种置换,对于每条边的出现与否,我们可以看作染色为黑白。

显然染色方案用Polya算一下,现在的问题是每种点置换中有多少种边等价类。

首先考虑一条边 ( u , v ) (u,v) (u,v),如果 u , v u,v u,v在同一个循环中,显然所有在循环中距离相等的点的连边在同一个等价类中,形式化地说,一个长度为 d d d的循环,两个端点都在其内部的边会形成 ⌊ d 2 ⌋ \lfloor\frac{d}{2}\rfloor 2d个等价类。

接下来还是考虑一条边 ( u , v ) (u,v) (u,v),如果 u , v u,v u,v不在同一个循环中,我们考虑将 u u u在循环中前进一步的同时 v v v在循环中也前进一步,则回到 u , v u,v u,v状态之前一共会遍历 l c m ( a , b ) lcm(a,b) lcm(a,b)个不同的边,其中 a , b a,b a,b分别是两个循环的长度,则这两个循环之间的边等价类有 a b / l c m ( a , b ) = g c d ( a , b ) ab/lcm(a,b)=gcd(a,b) ab/lcm(a,b)=gcd(a,b)个。

发现边等价类个数只和循环大小有关系。

直接暴力枚举有序正整数拆分。则一个有序正整数拆分对应的原图有 n ! ∏ L i ∏ c n t [ i ] ! \dfrac{n!}{\prod L_i\prod cnt[i]!} Licnt[i]!n!

其中 L i L_i Li是拆分中第 i i i个数的大小,也就是第 i i i个循环的长度,其实就是考虑固定开头就行了,后面 c n t [ i ] cnt[i] cnt[i]是长度为 i i i的循环个数,考虑所有 c n t [ i ] cnt[i] cnt[i]个循环互换位置可以得到上面的式子。

等价类个数和对应的原图方案数可以在枚举拆分的同时算出来。

最后需要除掉一个 n ! n! n!,那么可以在算方案数的时候直接不算 n ! n! n!


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int mod=997;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline int dec(int a,int b){a-=b;return a+(a>>31)&mod;}
inline int mul(int a,int b){int r=a*b;return r>=mod?r%mod:r;}
inline int power(int a,int b,int res=1){
	for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));
	return res;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
int inv[mod];

inline void init_inv(){
	inv[0]=inv[1]=1;
	for(int re i=2;i<mod;++i)inv[i]=mul(mod-mod/i,inv[mod%i]);
}

cs int N=65;
int n;
int g[N][N],pw[N*N];

int b[N],tot,ans;
int typ[N],t;
int cnt[N];

void dfs(int last,int rest,int coef,int sum){
	if(rest==0){
		Inc(ans,mul(coef,pw[sum]));
		return ;
	}
	for(int re i=std::min(last,rest);i;--i){
		int c=coef,s=sum;
		s+=i/2;
		for(int re k=1,j;k<=t;++k){
			j=typ[k];
			s+=cnt[j]*g[i][j];
		}
		++cnt[i];if(i!=last)typ[++t]=i;
		Mul(c,mul(inv[i],inv[cnt[i]]));
		dfs(i,rest-i,c,s);
		--cnt[i];if(i!=last)--t;
	}
}

signed main(){
#ifdef zxyoi
	freopen("graph.in","r",stdin);
#endif
	scanf("%d",&n);
	init_inv();
	pw[0]=1;for(int re i=1;i<N*N;++i)pw[i]=add(pw[i-1],pw[i-1]);
	for(int re i=1;i<=n;++i)
	for(int re j=1;j<=n;++j)g[i][j]=std::__gcd(i,j);
	dfs(n+1,n,1,0);
	cout<<ans<<"\n";
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值