zoukankan      html  css  js  c++  java
  • 无标号树的计数原理(组合计数,背包问题,隔板法,树的重心)

    闲话

    一个计数问题入门级选手来搞这种东西

    最初的动力来自高一化学课有机物(滑稽)。《同步导练》出了个这样的选择题。

    一个结构极其庞大的烷烃(二十几个碳原子),求它的主链长度。

    这不是个求树的直径的裸题么?!OI选手扫两眼就出来了,然而别的同学费劲心思找完了还是错的。

    于是第一次在常规课中体验到作为OIer的优越感。。。。。。

    又是一节课,芙蓉姐开始要我们画己烷、庚烷的同分异构体?!

    这不是等于要求节点数为(n),点度数不超过(4)的无标号的无根树个数吗?没见过,但还是有一点DP思想,蒟蒻开始手动枚举直径,不一会儿也画出来了。

    下课以后问芙蓉姐有没有公式。“这个东西要靠计算机知识来推导啦!”

    又一次在常规课中体验到作为OIer的优越感。。。。。。

    无标号有根树计数

    就是有多少种(n)个点,每个点度数限制为(m)的不同形态的无根树。比如说求烷基就是度数限制为(4)的有根树(那个未配对的键当成根就行了)。

    放__debug大佬的博客(戳这里

    叉姐过来的时候也聊了聊这个有趣的东西。

    先设(f_{i,j})(i)个点,根节点度数为(j)的方案数。显然(sumlimits_{j=0}^mf_{n,j})就是答案。

    再设(a_i=sumlimits_{j=0}^{m-1}f_{i,j})。这是个子树背包转移过程中的状态量,去掉(f_{i,m})是因为在树中只有根能有(m)个子节点,而其它点都被父节点占去了一个点度。

    引入一个Trick:

    如果把树(除根以外)分成若干部分,每个部分都由(k_s)个大小为(s)的子树构成,那么这一部分的方案数是(inom{a_s+k_s-1}{k_s})

    这个要用隔板法解释。

    简要介绍一下隔板法——把(n)个物品分成(m)段的方案数为(inom{n+m-1}{m-1})(允许有的段为空)

    证明的话,可以把分成(m)段看成在物品序列中插入(m-1)个隔板,相邻隔板(或隔板与序列末端)间就是一段。也就等于有长度为(n+m-1)的序列,钦定其中(m-1)个为隔板,剩下(n)个就是物品了。

    (k)个子树中,直接钦定每一个选什么,我们并不能计数。因此要运用逆向思维,把(k)个子树分成(a_s)组,每一组对应一种形态(仔细想想)。这样方案数就可以轻易地写出来了,为(inom{a_s+k_s-1}{a_s-1})。然而(a_s)很大,所以在计算过程中一般写(inom{a_s+k_s-1}{k_s})

    然后求和可以写成这种很不靠谱的式子

    [f_{i,j}=sum_{k_1+2k_2+...+jk_j=i}prod_{s=1}^jinom{a_s+k_s-1}{k_s} ]

    这个样子要怎么统计才好呢?

    利用DP中的常用思想,保证转移的有序性,我们可以限制转移中出现的最大子树大小(mx),再用背包进行转移。具体来说,先从小到大枚举(mx),对于每一个(i,j),我们可以枚举大小为(mx)的子树个数(k),从(f_{i-kcdot mx,j-k})处转移,方程为

    [f_{i,j}leftarrow sum_{k=1}^{max{j,lfloorfrac i{mx} floor}}f_{i-kcdot mx,j-k}inom{a_{mx}+k-1}{k} ]

    试想一下,因为对于每一种情况,最大的子树大小及其个数是唯一的,所以我们就做到了不重不漏。

    算上阶乘需要的时间,复杂度大概是(O(n^2mlog m))的样子。如果是烷基,(m)是常数,复杂度就是(O(n^2))

    题目

    LOJ6185 烷基计数

    然而组合数要取模,不知如何是好。交过这题的Dalao们似乎有另一种优秀的做法?蒟蒻再去学习学习。

    无标号无根树计数

    惊奇地发现__debug巨佬最初跟我想的一样,枚举直径,转而枚举直径的一半进行转移,然而是个(O(n^4))的垃圾算法(貌似前缀和一下可以优化到(O(n^3))?)

    然后就知道了要去对以重心为根的有根树计数。好有道理啊!因为重心在树中(至少在奇数个点的树中)是唯一的。

    问题就转化为有根树计数。只有两个地方不一样。一个是为了保证最后的根是重心,我们强制(mx<lceilfrac n 2 ceil)。另一个是由前一个引出的,因为假如(n)为偶数,那么可能会有两个重心。这两个重心的子树大小都是(frac n 2),所以沿用上面那个隔板法式子就好了。最终结果的方程为

    [ans=sum_{j=0}^{m}f_{n,j}+[nmod2=0]inom{a_{frac n 2}+1}2 ]

    题目

    [BZOJ4271]chemistry(可以去这里交)

    [Tsinsen1287]数树(点击进入题目

    然而都需要写高精度。。。。。。

    第二个就是点数为(frac{n-2}{m-1})度数为(m)的无根树计数,可以理解为把叶子节点都去掉

    然而(n=6002)太丧了,连__debug巨佬的python都要跑10s的样子。我再去给高精度卡常估计也没什么用了。生成函数是什么?置换群是什么?以后再填坑吧!

    第一个的代码

    #include<iostream>
    using namespace std;
    //高精度太长了不粘了,可以看蒟蒻以前的blog,也可以套个其它的版子进来
    const int N=509,m=4;
    bign f[N][N];
    inline bign C(RG bign n,RG int m){
    	RG bign ret=n;RG int i;
    	for(i=m;i>1;--i)ret*=--n;
    	for(i=m;i>1;--i)ret/=i;
    	return ret;
    }
    int main(){
    	f[1][0]=1;//初值
    	RG int n,i,j,k,mx;
    	RG bign a,ans;
    	cin>>n;
    	for(mx=1;mx<(n+1)>>1;++mx){//枚举限制
    		for(a=j=0;j<m;++j)
    			a+=f[mx][j];//预处理a
    		for(i=n;i>mx;--i)//从大到小,防止重复计数
    			for(j=1;j<=m;++j)
    				for(k=1;k<=j&&mx*k<i;++k)
    					f[i][j]+=f[i-mx*k][j-k]*C(a+k-1,k);
    	}
    	for(ans=j=0;j<=m;++j)
    		ans+=f[n][j];
    	if(!(n&1)){//特判双重心
    		for(a=j=0;j<m;++j)
    			a+=f[n>>1][j];
    		ans+=C(++a,2);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    南阳理工ACM(题目56)
    南阳理工ACM(题目56)
    南阳理工ACM(题目56)
    csuoj1009
    素数槽csuoj
    简单动态规划问题分析
    sort函数使用的基本知识
    2014年7月19日——比赛题取石头问题1
    CODEVS——T 1269 匈牙利游戏 2012年CCC加拿大高中生信息学奥赛
    洛谷—— P1640 [SCOI2010]连续攻击游戏
  • 原文地址:https://www.cnblogs.com/flashhu/p/9457830.html
Copyright © 2011-2022 走看看