bzoj1488 [HNOI2009]图的同构

9 篇文章 0 订阅
4 篇文章 0 订阅

http://www.elijahqi.win/archives/3429
Description

求两两互不同构的含n个点的简单图有多少种。

简单图是关联一对顶点的无向边不多于一条的不含自环的图。

a图与b图被认为是同构的是指a图的顶点经过一定的重新标号以后,a图的顶点集和边集能完全与b图一一对应。

Input

输入一行一个整数N,表示图的顶点数,0<=N<=60

Output

输出一行一个整数表示含N个点的图在同构意义下互不同构的图的数目,答案对997取模。

Sample Input

输入1
1
输入2
2

输入3
3

Sample Output

输出1
1
输出2
2

输出3
4

题目可以考虑等价为给每条边染色 选那么就是染1 反之就是染0那么就是求这样的方案数是多少

直接考虑边的同构 并不可做 那么我们不妨考虑通过点的置换来算边的置换

观察点置换可以发现结构相同的点置换->循环数相同的置换 即边的循环数和点的循环数有关系

至于什么关系后面认真观察

通过观察可以发现一条边他的两个点要么在同一个点置换要么在两个点置换

那么先考虑简单的情况 在两个点置换中 那么我们通过多枚举样例可以感知到假设我现在有两个点置换

大小为l1,l2那么他们构成的边个数就是l1*l2 然而考虑他们两个置换构成的边的循环周期(长度)是lcm(l1,l2)

那么循环个数就是l1*l2/(l1*l2/gcd(l1,l2))=gcd(l1,l2)

那么考虑在一个置换里的边是什么情况

点置换为偶数的时候有个特例即覆盖l/2的那个循环实际大小只是1 所以我们最后加一即可

奇数的时候每个循环覆盖l条边 总共c(l,2)条边 这么算一算即可

那么归结起来奇数和偶数点置换最后都会是l/2下取整这么多的边循环

直接暴力枚举显然复杂度不正确 但是 发现边循环和点循环并不有直接关系 仅仅和循环大小有关系

所以我们不妨假设来枚举一个数列l1<=l2<=l3…且l1+l2+l3+..=n这样的话 再算出这样的点置换有多少个一乘即可 复杂度大约∑i*log(i)

那么考虑这样一共有多少大小分别为l1,l2,l3这样的循环的排列

最后的个数为 n!|l1||l2||l3|..s1!s2!s3! n ! | l 1 | ∗ | l 2 | ∗ | l 3 | ∗ . . ∗ s 1 ! ∗ s 2 ! ∗ s 3 ! 其中s1,s2分别为长度为1的置换出现的次数 长度为2的置换出现的次数 因为这样做的话我们可以考虑全排列相比这些循环映射多了什么 在一个循环映射里1,2 2,1算一种 但是全排列却算了两次所以便有了多出的这些方案 于是我们直接除掉即可因为考虑如果是循环大小相同的也会因为互相的相对位置被多算所以也除掉即可

最后利用上面算出的东西结合polya即可得到答案
先枚举点置换情况 算出边置换的个数 应用polya 每个循环可以选择填或者不填所以就算出方案然后 再 × × 这种情况下一共有多少个这样的边置换 最后在整体/边置换总数即可
边置换总数为n!

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(!isdigit(ch)) {if (ch=='-') f=-1;ch=gc();}
    while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
    return x*f;
}
const int N=1100;const int mod=997;
inline int gcd(int x,int y){return !y?x:gcd(y,x%y);}
inline int ksm(int b,int t){static int tmp;
    for (tmp=1;t;b=b*b%mod,t>>=1) if (t&1) tmp=tmp*b%mod;return tmp;
}
int ans,n,v[N],jc[N],nm[N],cnt;
inline void dfs(int now,int left){
    if (left==0){
        int tmp1=0,tmp2=1;
        for (int i=1;i<=cnt;++i){
            tmp1+=nm[i]*(nm[i]-1)*v[i]>>1;tmp1+=v[i]/2*nm[i];
            for (int j=i+1;j<=cnt;++j) tmp1+=nm[i]*nm[j]*gcd(v[i],v[j]);
        }
        for (int i=1;i<=cnt;++i)
            tmp2=tmp2*ksm(v[i],nm[i])*jc[nm[i]]%mod;
        tmp2=ksm(tmp2,mod-2)*jc[n]%mod;ans=(ans+tmp2*ksm(2,tmp1))%mod;
        return;
    }
    if(now>left) return;dfs(now+1,left);
    for (int i=1;i*now<=left;++i){
        v[++cnt]=now,nm[cnt]=i;dfs(now+1,left-i*now);--cnt;
    }
}
int main(){
    freopen("bzoj1488.in","r",stdin);
    jc[0]=1;for (int i=1;i<=100;++i) jc[i]=jc[i-1]*i%mod;
    n=read();dfs(1,n);ans=ans*ksm(jc[n],mod-2)%mod;printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值