图的同构
题目描述
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1488
题解
还在搞OI的时候就看过这道题,看的一脸懵逼。
学了群论的相关知识再来看这题,还是一脸懵逼。
后来借助题解知道了做法。
求n个点完全不同的简单无向图的方案数。
对于每条边,存在与不存在等价于黑白染色。
因为涉及到点的置换,边的置换,所以考虑polya定理。
枚举点的置换群的点循环的循环大小
对于同一个点循环内的边循环的大小等于点循环大小/2
对于不同点循环内边循环大小等于两个点循环大小的gcd。
这个手玩一下应该看得出来为什么
所以sum=Σ2^(Σsize[i]/2+Σgcd(size[i],size[j]))
接下来求符合该循环集条件下的边置换的个数,即对应的点置换的个数。
tmp=n!/π( size[i])/(π num[i]!)
其中num[i]表示大小为size[i]的循环节的个数
除掉size[i]可以理解为圆形排列
除掉num[i]!是因为相同大小的循环节会出现重复计算
ans=Σsum*tmp/n!
题解参考的这份:
http://blog.csdn.net/wzq_qwq/article/details/48035455
然后这题也可以手完几个点然后去查OEIS(逃)
双倍经验 bzoj1815
代码
#include<bits/stdc++.h>
#define mod 997
#define N 65
using namespace std;
int n,ans,val[N],s[N],fac[N];
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
int gcd(int a,int b)
{
if(!b)return a;
return gcd(b,a%b);
}
void dfs(int x,int res,int num)
{
if(!res)
{
int sum=0,tmp=1;
for(int i=1;i<=x;i++)
{
sum+=s[i]*(s[i]-1)/2*val[i]+val[i]/2*s[i];
for(int j=i+1;j<=x;j++)
sum+=gcd(val[i],val[j])*s[i]*s[j];
}
for(int i=1;i<=x;i++)
tmp=tmp*fac[s[i]]*Pow(val[i],s[i])%mod;
tmp=Pow(tmp,mod-2)*fac[n]%mod;
ans=(ans+Pow(2,sum)*tmp)%mod;
return;
}
if(num>n)return;
dfs(x,res,num+1);
for(int i=1;i*num<=res;i++)
{
val[x+1]=num;s[x+1]=i;
dfs(x+1,res-i*num,num+1);
}
}
int main()
{
scanf("%d",&n);
fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
dfs(0,n,1);
printf("%d\n",ans*Pow(fac[n],mod-2)%mod);
return 0;
}