题解 P4727 【[HNOI2009]图的同构记数】

斯德哥尔摩

2018-07-29 18:21:24

Solution

这题感觉像是$Polya$计数。。。 然后借鉴了这位大佬的博客证明了这题是$Polya$计数:[博客](https://blog.csdn.net/sdfzyhx/article/details/70035662) 首先,我们可以把问题转化成在完全图上对边进行黑白染色,求不同构方案数。 然后就跟这题差不多了:[P4128 [SHOI2006]有色图](https://www.luogu.org/problemnew/show/P4128) 对于每个点的置换,要求出有多少关于边的不动点。 把置换写成循环,可以发现两条性质: 1. 一个长度为$x$的点的循环内部有$\lfloor \frac{x}{2}\rfloor$个边的循环。 2. 两个长度分别为$x$和$y$的点的循环之间有$gcd(x,y)$个边的循环,这样关于边的不动点总数就是$2m$,其中$m$表示边的循环的总数。 但是直接枚举点的置换有$n!$种,$TLE$。 我们考虑,因为每种置换对答案的贡献只和每个循环的大小有关,可以枚举$n$的正整数拆分,然后对每种拆分计算对应的置换个数。 具体来说,记有$m$个循环,每个循环的长度为$l_i$,每个长度的循环有$c_i$个。 首先给每个循环分配一个位置: $$\left(\begin{array}{c}n\\l_1\end{array}\right)\left(\begin{array}{c}n-l_1\\l_2\end{array}\right)...\left(\begin{array}{c}n-l_1-l_2-...-l_{m-1}\\l_m\end{array}\right)$$ 然后固定第一个元素,每个循环有 $(l_i-1)!$ 种排法。 但是相同长度的循环彼此之间的顺序被重复考虑。 所以最后的答案就可求了: $$Ans=\frac{n!}{\prod_{i=1}^ml_i\prod_{i=1}^mc_i}$$ 除转乘直接逆元,而$997\in prime$,所以直接费马小定理即可。 附代码: ```cpp #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #define MAXN 70 #define MOD 997 using namespace std; long long n,ans=0; long long val[MAXN],num[MAXN],fact[MAXN],inv[MAXN],invfact[MAXN],gcd[MAXN][MAXN]; inline long long read(){ long long date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } long long Gcd(long long x,long long y){//记忆化gcd if(gcd[x][y])return gcd[x][y]; else if(!y)return x; return Gcd(y,x%y); } long long mexp(long long a,long long b,long long c){//快速幂应该都会 long long s=1; while(b){ if(b&1)s=s*a%c; a=a*a%c; b>>=1; } return s; } void get_answer(){//累计答案 int top=0; long long sum=0,now=fact[n]; for(int i=1;i<=n;i++){ now=now*invfact[num[i]]%MOD; for(int j=1;j<=num[i];j++){ val[++top]=i; now=now*inv[i]%MOD; } } for(int i=1;i<=top;i++){ sum+=val[i]/2; for(int j=i+1;j<=top;j++)sum+=gcd[val[i]][val[j]]; } now=now*mexp(2,sum,MOD)%MOD; ans=(ans+now)%MOD; } void dfs(int x,int s){//直接暴搜 if(x==1){ num[1]=n-s; get_answer(); return; } for(int i=0;s+i*x<=n;i++){ num[x]=i; dfs(x-1,s+i*x); num[x]=0; } } void work(){ dfs(n,0); ans=ans*invfact[n]%MOD;//别忘了! printf("%lld\n",ans); } void init(){//预处理阶乘、逆元、gcd n=read(); fact[0]=invfact[0]=1; for(int i=1;i<=n;i++){ fact[i]=fact[i-1]*i%MOD; inv[i]=mexp(i,MOD-2,MOD); invfact[i]=mexp(fact[i],MOD-2,MOD); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) gcd[i][j]=Gcd(i,j); } int main(){//主函数So easy! init(); work(); return 0; } ```