BZOJ 1488 [HNOI2009]图的同构 Polya定理
2015-08-27 21:01
337 查看
题意:链接
方法: Polya定理
解析:
先扯点题外话。
小雨淅沥的下午,PoPoQQQ爷在屠了一道题后放松心情,恰看见我把知识点上的群论标记已会。于是,为了发扬D人的精神,PoPoQQQ爷打开了BZOJ,给我找了这么一个题,说:”这题都没做过还敢说会群论?”
……
……
征战半下午,卒。
(我好菜以后再也不敢说我会啥了T_T
(做这道题千万别去OEIS找通项=-=)
好不扯了,言归正传说这道题怎么做。
这道题确实是个好题,跟以前的群论的解法有共同点但是又有新的东西。
反正我他妈是没推出来。太难辣。
首先题意既然是求不同构的图的个数。
并且点可以任意置换。
所以我们可以这么想这道题。
即所有的边,选取为1,不选取为0。
这就是个染色问题吗。
然后再上个Polya什么的就搞定了。
然而发现,这他妈不对啊。
我们可?求?点的置换,边怎么办啊!
点的置换显然n!,即全排列。
但是边呢!
然后我就死在这里了。
怎么找呢?
不急,我们先来看点的选法。
首先我们肯定能给这个n个点划分一下。
划分成若干部分,每一部分之间可以互相置换。
好像说的有点扯?
其实就是我们来划分一下循环。
每一种划分是一个置换。
然后我们来考虑边,
对于一条边。
它的两个顶点要么是在同一个循环内,要么是在两个循环内。
如下是一个点循环为5的图,显然图中的红色边集体是一个边循环,绿色边集体是一个边循环。
这个画画图就出来了。
其实要证好像也不难?
首先最外层的一堆边肯定是一个循环。
然后我们隔一个连边又是一个循环。
隔两个又是..
直到什么时候呢?
比如这个五边形。
当我们隔两个连的时候就相当于反向连了一个隔一个地边。
这显然之前已经连过了,所以就没有增广循环了。
那么我们就得出来了一个结论,对于在同一个点循环内部的边循环个数,是点循环大小除2。
那么不在一个点循环内的两个点之间的边呢?
图我只能说我尽力了。
一个点循环大小为6,另一个为3,显然有3个边循环。
所以规律呢?
您画一遍就知道是GCD了。
这样我们就研究完了点循环转化边循环。
N的范围是60,我们要划分N的话据说是10^6数量级的。如果我们暴力搞,是不行的。
所以哪里出问题了呢。
让我们来想一个问题。
对于3个点来说,显然排列有6种,但是呢,其中有三种都是一个大小为1的点循环加上一个大小为2的点循环。
也就是说这个方案我们多枚举了两次。
所以我们要优化掉这个东西的话就差不多了。
怎么实现呢?
我们不妨来枚举当前枚举到的点循环的大小。然后在枚举一下个数。
这样我们枚举的复杂度大概是多少呢?
我只算大概。
那我们就不妨把这个枚举复杂度定为O(∑ni=1i∗log(i))O(\sum_{i=1}^{n}i*log(i))
现在来考虑我们枚举完了之后怎么做。
枚举完了之后,因为颜色只有两种(选边或者不选)
所以我们要统计一下边循环的个数。
怎么统计我上面已经说了。
不妨设统计完是x个边循环。
现在对于我们枚举出来的一个点循环集l1,l2...lm{l_1,l_2...l_m}来说,有多少个这个点循环集呢?
有N!|l1|∗|l2|∗...∗|lm|\frac{N!}{|l_1|*|l_2|*...*|l_m|}
但是注意到,我们枚举出来的点循环集可能有大小相等的。
所以我们还得除掉大小相等的点循环集的个数的排列。
至于如上为什么是这样,您要是不知道的话,那请听一听排列组合吧。
于是对应的这个点循环搞出来的如此的边循环集个数是多少呢N!|l1|∗|l2|∗...∗|lm|∗|S1!|∗|S2!|∗...∗|Sq!|\frac{N!}{|l_1|*|l_2|*...*|l_m|*|S_1!|*|S_2!|*...*|S_q!|}
S的定义上文我已经说过了。
然后加上polya定理就搞定了。
但是总置换个数是多少呢?(全排列)
N!…
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 1010 #define mod 997 using namespace std; int n,cnt,ans; int powtwo ,factor ,invfac ,num ,val ,gcd ,inv ; int get_gcd(int x,int y) { while(y) { int t=y; y=x%y; x=t; } return x; } int get_inv(int x,int y) { int ret=1; while(y) { if(y&1)ret=(ret*x)%mod; x=(x*x)%mod; y>>=1; } return ret; } void init() { powtwo[0]=factor[0]=invfac[0]=1; for(int i=1;i<=mod;i++) { factor[i]=factor[i-1]*i%mod; } } void dfs(int now_num,int left) { if(left==0) { int retnow=0; int bot=1; for(int i=1;i<=cnt;i++) { retnow+=num[i]*(num[i]-1)/2*val[i]+val[i]/2*num[i]; for(int j=i+1;j<=cnt;j++) retnow+=num[i]*num[j]*get_gcd(val[i],val[j]); } for(int i=1;i<=cnt;i++) { bot=(bot*get_inv(val[i],num[i])%mod*factor[num[i]])%mod; } bot=get_inv(bot,mod-2)*factor %mod; ans=(ans+get_inv(2,retnow)*bot%mod)%mod; } if(now_num>left)return; dfs(now_num+1,left); for(int i=1;i*now_num<=left;i++) { val[++cnt]=now_num,num[cnt]=i; dfs(now_num+1,left-i*now_num); cnt--; } } int main() { scanf("%d",&n); init(); dfs(1,n); ans=ans*get_inv(factor ,mod-2)%mod; printf("%d\n",ans); }
相关文章推荐
- 【转载】ACM总结——dp专辑
- 透明与Z序示例
- Websocket 概述
- RAID的简单认识
- UVa 1583 Digit Generator
- Hbase coprocessor获取数据
- 设计一个聊天服务器
- Session与Cookie
- Word Search
- UNIX网络编程8 从图中了解TCP协议在Linux内核中的实现
- 埃及分数 把一个分数分解成n个 m分之一的方式
- 游标
- SAP BAPI资产过账问题随笔
- 之前UI的小练习
- web服务器apache理论、实践详解 ,TCP/IP
- HDOJ 4508 湫湫系列故事——减肥记I(完全背包)
- 静态库打包教程
- UI中的界面之间的值传递 <二>
- POJ 2828 Buy Tickets
- [leetcode]Contains Duplicate C语言