BZOJ1488 [HNOI2009]图的同构

16 篇文章 0 订阅
3 篇文章 0 订阅

我们可以把每条边存在或者不存在看成是黑和白两种颜色

然后这个题就充斥着一股ploya定理的气息

但是有关边的置换太蛋疼,我们考虑把点的置换对应到边上

找找规律,我们发现对于一个点的置换,如果其存在一个循环节大小为x,那么这x个点的边之间会形成x/2个循环节,如果其存在两个循环节大小分别为x和y,那么两个循环节里的点之间的边会形成gcd(x,y)个循环节

我们考虑爆搜点置换,每次枚举当前最大的循环节多大,有多少个(不要问我为什么这么搜复杂度是对的,我并不懂)

那么假设当前有cnt种循环节,每种的大小是v[i],每种有t[i]个,那么这样的置换一共就有n!/v[1]v[2]v[3]...v[cnt]t[1]!t[2]!t[3]!...t[cnt]!种

然后就可以算了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<map>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
using namespace std;
#define MAXN 70
#define MAXM 1010
#define INF 1000000000
#define MOD 997
#define eps 1e-8
#define ll long long
int fac[MAXN],ine[MAXN],inv[MAXN];
int n;
int v[MAXN],t[MAXN];
int tot;
int ans;
int gcd(int x,int y){
	return !y?x:gcd(y,x%y);
}
int mi(int x,int y){
	int re=1;
	while(y){
		if(y&1){
			(re*=x)%=MOD;
		}
		(x*=x)%=MOD;
		y>>=1;
	}
	return re;
}
void dfs(int x,int rem){
	int i,j;
	if(!rem){
		
		int tmp=fac[n];
		int cnt=0;
		for(i=1;i<=tot;i++){
			(tmp*=ine[t[i]]%MOD)%=MOD;
			for(j=1;j<=t[i];j++){
				(tmp*=inv[v[i]])%=MOD;
			}
		}
		for(i=1;i<=tot;i++){
			cnt+=t[i]*(v[i]/2);
			cnt+=t[i]*(t[i]-1)/2*v[i];
			for(j=1;j<i;j++){
				cnt+=t[i]*t[j]*gcd(v[i],v[j]);
			}
		}
		(ans+=tmp*mi(2,cnt))%=MOD;
		return ;
	}
	if(x>rem){
		return ;
	}
	dfs(x+1,rem);
	for(i=1;i*x<=rem;i++){
		v[++tot]=x;
		t[tot]=i;
		dfs(x+1,rem-i*x);
		tot--;
	}
}
int main(){
	int i;
	fac[0]=ine[0]=inv[0]=inv[1]=1;
	for(i=1;i<MAXN;i++){
		fac[i]=fac[i-1]*i%MOD;
	}
	for(i=2;i<MAXN;i++){
		inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
	}
	for(i=1;i<MAXN;i++){
		ine[i]=inv[i]*ine[i-1]%MOD;
	}
	scanf("%d",&n);
	dfs(1,n);
	(ans*=ine[n])%=MOD;
	printf("%d\n",ans);
	return 0;
}

/*

*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值