• 【BZOJ1488】[HNOI2009]图的同构计数


    题目链接

    题意

    求 n 个点的同构意义下不同的图的数量。((nleq 60))

    Sol

    (Polya) 定理的练手题。
    我们这里先把边的存在与否变成对边进行黑白染色,白色代表不存在,这样就变成了一个对完全图中的边进行染色的问题,于是可以使用 对(Burnside)引理 进行优化后的 (Polya) 定理。

    显然总的置换群大小是 (n!) 对应了每一种对边进行重新编号的方案,关键就在于要求出不动点的个数。
    使用 (Polya) 定理,那么只需要求出所有关于边的置换中每一个置换被分成的循环总个数(也就是等价类总个数)。
    显然每一个对于点的置换都唯一对应一个对于边的置换。但是即使是这样,置换数目依然是不可枚举的。所以我们肯定要另求他法。
    由于最后的计算只和被分解成的循环个数有关,于是我们可以爆搜出所有的整数拆分方案代表最后循环的格式。
    考虑可能处于一个等价类的边集要么这些边的端点都位于一个点的循环内部,要么这些边的端点仅连接了两个不同的循环。
    首先考虑一个循环内部边集的等价类个数,假设有 n 个点,那么只用考虑这 n 个点的完全图中的边。我们把这些点排成一排,这样每一条边就有了一个跨度,循环一次相当于把这些点全部往前挪动一位并且把最前面的点扔到最后面去,这样显然跨度相同的边会在一个等价类里,然后由于这是一个环 那么跨度为 (d) 的和跨度为 (n-d+1) 的也是等价的 , 跨度总共有((n-1))种于是总共有 (lfloor frac{n}{2} floor) 种等价类。
    然后考虑两个循环之间的等价类个数 , 假设大小为别为 (a,b) , 我们把两个循环就看作是一个环 , 每次置换相当于是把两个环同时转动一圈 , 这样子总共会便历到的边就有 (lcm(a,b)) 条 , 然后边总共有 (a*b) 条,于是就有 (frac{a*b}{lcm(a,b)}=gcd(a,b)) 种等价类。

    这样我们就算完一种类型的置换的贡献了,显然这样的置换是有许多个的,我们还要乘上一个系数,也就是满足我们整数拆分中枚举的格式的置换个数 , 考虑一个排列然后把重复的去掉即可,总数为:

    [dfrac{n!}{prod_{i=1}c_i!*i^{c_i}} ]

    (c_i)表示大小为 (i) 的循环个数
    我们把一个排列直接认为是把循环按大小顺序排好的结果。
    这样对于相同大小的循环,他们随便排列都是没有关系的,所以除掉 (c_i!) , 然后对于大小为 i 的一个循环来说 , 它的内部不断循环也是相同的方案 , 所以每一个大小为 (i) 的循环都要在方案中再除掉 (i)

    code:

    #include<bits/stdc++.h>
    #define Set(a,b) memset(a,b,sizeof(a))
    using namespace std;
    const int N=61;
    const int mod=997;
    template <typename T> inline void init(T&x){
    	x=0;char ch=getchar();bool t=0;
    	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
    	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    	if(t) x=-x;return;
    }
    typedef long long ll;
    typedef unsigned long long ull;
    template <typename T>inline void Inc(T&x,int y){x+=y;if(x>=mod) x-=mod;return;}
    template <typename T>inline void Dec(T&x,int y){x-=y;if(x <  0) x+=mod;return;}
    template <typename T>inline int fpow(int x,T k){int ret=1;for(;k;k>>=1,x=(ll)x*x%mod) if(k&1) ret=(ll)ret*x%mod;return ret;}
    int Sum(int x,int y){x+=y;if(x>=mod) return x-mod;return x;}
    int Dif(int x,int y){x-=y;if(x < 0 ) return x+mod;return x;}
    int n;
    int size[N],num[N],top=0,rest=0,cnt=0,mx=0;
    int Gcd[N][N],bits[N],finv[N];
    int ans=0;
    inline int gcd(int a,int b){return b? gcd(b,a%b):a;}
    inline void Calc(){
    	int nomove=0;int Inv=1;
    	for(int i=1;i<=top;++i) {
    		nomove+=num[i]*(size[i]>>1);
    		Inv=Inv*finv[num[i]]*fpow(fpow(size[i],num[i]),mod-2)%mod;
    		nomove+=num[i]*(num[i]-1)/2*size[i];
    		for(int j=i+1;j<=top;++j) {
    			int g=Gcd[size[i]][size[j]];
    			nomove+=g*num[i]*num[j];
    		}
    	}
    	Inc(ans,fpow(2,nomove)*Inv%mod);
    	return;
    }
    void Dfs(int pre){
    	int now=rest;if(!rest) {Calc();return;}
    	for(int i=pre+1;i<=now;++i) {
    		for(int j=1;j*i<=now;++j) {
    			rest=now-i*j;
    			size[++top]=i,num[top]=j;
    			Dfs(i);--top;
    		}
    	}
    }
    int main()
    {
    	init(n);if(!n) return puts("1"),0;bits[0]=1,finv[1]=1,bits[1]=2;
    	for(int i=2;i<=n;++i) bits[i]=Sum(bits[i-1],bits[i-1]),finv[i]=(ll)(mod-mod/i)*finv[mod%i]%mod;
    	for(int i=2;i<=n;++i) finv[i]=(ll)finv[i-1]*finv[i]%mod;
    	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) Gcd[i][j]=gcd(i,j);
    	rest=n;Dfs(0);cout<<ans<<endl;
    	return 0;
    }
    
    
  • 相关阅读:
    ReentrantLock的实现原理及AQS和CAS
    lock、tryLock和lockInterruptibly的差別
    Sleep和Wait的区别
    什么场景使用多线程
    多生产者多消费者,生产一个数据,消费一个数据
    线程间通讯
    如何退出线程
    线程的状态
    判断二叉树是不是完全二叉树
    js2048小游戏
  • 原文地址:https://www.cnblogs.com/NeosKnight/p/10561758.html
Copyright © 2020-2023  润新知