bzoj1488 [HNOI2009]图的同构 Polya定理

首先,这道题可以看做将边用两种颜色(存在和不存在)染色…

然后我就想起了我的远古文章,想当年,我还是个对Polya定理一知半解的小蒟蒻,时过境迁,如今我已经变成了一个完全不知道Polya定理是什么东西的大蒟蒻了。

具体的可以去看那篇文章,我梳理一下这个模型。

  1. 两端点都在大小为 L L L的同一个点循环节中的边循环节数量为 ⌊ L 2 ⌋ \lfloor \frac{L}{2} \rfloor 2L,在大小为 L 1 L_1 L1 L 2 L_2 L2的两个点循环节中的边循环节数量为 g c d ( L 1 , L 2 ) gcd(L_1,L_2) gcd(L1,L2)
  2. 暴力枚举点被拆分成哪些大小的循环节。
  3. 考虑将 n n n个不同编号的点插入这些大小的循环节中,考虑固定循环节表示法的第一个点后剩下的点任意排列都是不同的置换,考虑大小相同的循环节的贡献会算重,总之这个循环节拆分可以对应的置换个数是 n ! L 1 L 2 . . . L m k 1 ! k 2 ! . . . k t ! \frac{n!}{L_1 L_2 ... L_m k_1!k_2!...k_t!} L1L2...Lmk1!k2!...kt!n!( k k k代表某一个大小的循环节数量)
  4. 使用Polya定理 1 ∣ G ∣ ∑ k c i \frac{1}{|G|}\sum k^{c_i} G1kci得到答案( c i c_i ci表示置换 i i i的循环节数量)。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=997;
int n,ans,gcd[62][62],fac[62],inv[62],ni[62],L[62],K[62],bin[3605];
int GCD(int x,int y) {return y?GCD(y,x%y):x;}
int qm(int x) {return x>=mod?x-mod:x;}
void work(int tot) {
	for(RI i=1;i<=n;++i) K[i]=0;
	for(RI i=1;i<=tot;++i) ++K[L[i]];
	int re=fac[n],js=0;
	for(RI i=1;i<=n;++i) re=re*ni[K[i]]%mod;
	for(RI i=1;i<=tot;++i) re=re*inv[L[i]]%mod;
	for(RI i=1;i<=tot;++i) {
		js+=L[i]/2;
		for(RI j=i+1;j<=tot;++j) js+=gcd[L[j]][L[i]];
	}
	ans=qm(ans+re*bin[js]%mod);
}
void dfs(int x,int tot,int las) {
	if(x==n) {work(tot);return;}
	for(RI i=1;i<=las&&x+i<=n;++i) L[tot+1]=i,dfs(x+i,tot+1,i);
}
int main()
{
	scanf("%d",&n);
	for(RI i=1;i<=n;++i)
		for(RI j=i;j<=n;++j) gcd[i][j]=GCD(i,j);
	fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod;
	bin[0]=1;for(RI i=1;i<=n*n;++i) bin[i]=qm(bin[i-1]+bin[i-1]);
	inv[0]=inv[1]=ni[0]=ni[1]=1;
	for(RI i=2;i<=n;++i)
		inv[i]=(mod-mod/i)*inv[mod%i]%mod,ni[i]=ni[i-1]*inv[i]%mod;
	dfs(0,0,n),ans=ans*ni[n]%mod;
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值