bzoj1478:Sgu282 Isomorphism
思路:由于题目中是通过改变点的编号来判断两种染色方案是否相同,而染色的确是边,于是考虑如何将点置换转化为边置换。
对于一个有n个点的完全图,其点置换有n!个(即全排列个数),又由于每一个边置换都对应了一个点置换(因为是改变点的编号才得到的边置换),因而边置换也是n!个,也就是说已经确定了Polya定理中的分母,考虑分子怎么求。
对于一个点置换,会形成一些循环,而这些循环所对应的边置换也是一个循环,同时,这些点循环之间的边也会形成一个循环,边置换中的总循环数就取决于点置换循环中对应的边置换循环和点置换循环间对应的边置换循环。
首先考虑来求点置换循环间对应的边循环数,其所对应边置换一个循环的长度是lcm(Li,Lj)(这里和下面的Li,Lj都表示点循环的长度),因为可以看成是一个长为Li的齿轮和长为Lj的齿轮,初始时有两个涂有颜色的槽嵌在一起,然后问经过多少个槽这两个点再次嵌在一起,很显然是lcm(Li,Lj),然后一共有Li*Lj条边,且循环间无交集,因此一共就是Li*Lj/lcm(Li,Lj)=gcd(Li,Lj)。
接着考虑如何来求点置换循环内部对应的边置换循环数,分奇环和偶环考虑,首先对于奇环显然对应每个边置换长度为Li,因为考虑一个环然后一条边连接任意两个点,然后每一次边连接的两个点都跳到下一个点,过了n次相当于又回到了一个起点,那么这就是一个边置换循环,那有没有可能更短呢?没有,如果令那条边连接的两个点为点A和点B,那么更短只可能是点A走到点B同时点B又走到点A,那么此时A,B恰好经过一个点置换环的长度,且A,B经过距离相等,那这就不再是一个奇环而是一个偶环了,所以假设不成立,因而n一定是最短的也是边置换循环的长度,然后一共就Li*(Li-1)/2条边,因此答案就是(li-1)/2条边。而对于偶环边置换长度同样也令它为Li,但依照前面去推就有个Bug,就是当选的边恰好是该环一条经过点的对称轴时,那么此时边置换的长度就是Li/2而不是Li,这样就相当于多算了Li/2条边,于是用总的Li*(Li-1)/2条边-Li/2条边作为总边数,最后答案加1即可,此时答案即Li/2条边,统一一下即:点置换循环内对应的边置换循环数即Li/2条边。
那么这样,m的指数也已经出来了,那这样总不能一个一个去累加吧,因为总数高达n!,但可以发现指数只和Li和Lj有关,而所有的Li之和就恰好是n,于是可以把Li相同的一起讨论,这样即n的拆分,而n的拆分并不大,只有30多万多种,这样就只需计算n的一种拆分再去乘以这种拆分下的有多少种即可,而这个多少种是有公式的,首先定义Bi表示n的拆分(即L集合)中等于i的个数,那么方案数就是n!/(L1*L2*...*Lk*B1!*B2!*B3!*...*Bk!)(蒟蒻并不知道这个公式怎么证明,但这好像是一个定理,而且组合数学应用本书上都好像是直接给出并没有证明QAQ)然后用逆元什么的搞一搞就好了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,p,top,ans; int l[100],frac[100],b[100]; int power(int a,int k,int p){ if (k==0) return 1; if (k==1) return a%p; int x=power(a,k/2,p),ans=1ll*x*x%p; if (k&1) ans=1ll*ans*a%p; return ans; } int calc(){ int ans=frac[n],div=1; for (int i=1;i<=top;i++) div=1ll*div*l[i]%p; for (int i=1;i<100;i++) if (b[i]) div=1ll*div*frac[b[i]]%p; return 1ll*ans*power(div,p-2,p)%p; } int gcd(int a,int b){return b==0?a:gcd(b,a%b);} int solve(){ int ans=0; for (int i=1;i<=top;i++) ans+=l[i]/2; for (int i=1;i<top;i++) for (int j=i+1;j<=top;j++) ans+=gcd(l[i],l[j]); return ans; } void dfs(int remain,int last){ if (!remain){ ans=(ans+1ll*calc()*power(m,solve(),p)%p)%p; return; } for (int i=min(remain,last);i;i--) l[++top]=i,b[i]++,dfs(remain-i,i),top--,b[i]--; } int main(){ scanf("%d%d%d",&n,&m,&p);frac[0]=1; for (int i=1;i<=n;i++) frac[i]=1ll*frac[i-1]*i%p; dfs(n,n);ans=1ll*ans*power(frac[n],p-2,p)%p; printf("%d\n",ans); return 0; }
UPD:一年以后来复习Polya的时候突然发现上面那个式子就是共轭类的个数的式子。。。。。。
假设我们已经得到了这些Li,按Li排序然后从左往右排成一个长为n的排列,一共有n!种排列,但显然有重复。
对于一个循环内,例如一个点循环的长度为k,例如(a1,a2,a3...ak),它循环左移若干位后得到的点循环是一样的,一共有k种,然后每一个点循环都会被多算,也就是要除以所有的Li。
然后考虑长度相同的点循环,例如一个点循环的长度为k,这样的点循环有Bk个,显然(a1,a2)(a3,a4)与(a3,a4)(a1,a2)是一样的,也就是说会被多算Bk!,然后就要除以所有的Bk!