烷基计数 题解

好怪的化学题……

题意:\(n\)个碳原子组成的烷烃同分异构体个数。或者,形式化的(如果您还没学到有机),求\(n\)个点,每个点度数\(\le4\),且根节点度数\(\le3\)的无标号有根树计数。(其实就是每个点最多三个儿子的树计数)

loj上这个有三版,普通版\((n\le400)\),加强版\((n\le5000)\),加强版加强版\((n\le100000)\)

首先说一下:\(n\)个元素选\(m\)次的方案数是\(C_{n+m-1}^m\)。考虑有哪些元素可以重复选。

  1. 普通版\(O(n^3)\)

我们设\(dp[i]\)是有\(i\)个碳原子的烷烃个数,然后显然最多有三个儿子,那就枚举一下总点数和两个子树的点数,暴力显然是\(O(n^3)\)的。

那么我们前面那个组合数有什么用呢?我们发现这三棵子树的点数可能是相等的,但是由于是无标号计数,当两个是相同情况的时候如果直接乘会重复计算。于是我们就需要前面那个组合数来统计答案。

inline int C(int n,int m){
	if(m==1)return n;
	if(m==2)return 1ll*(n+1)*n/2;
	if(m==3){
		return n*n+n*(n-1)*(n-2)*inv6;//inv6是6的逆元 
	}
	return 0;//看不懂就把组合数拆开 还有这题有点卡常少取点模 
}
for(int i=1;i<=n;i++){
	for(int j=0;j<=i;j++){
		for(int k=j;j+k<=i;k++){
			int l=i-(j+k);
			if(l<k)continue;//防止计重 
			if(j==k&&k==l)dp[i]+=C(dp[j],3);//均省略取模以增加可读性 
			else if(j==k)dp[i]+=C(dp[j],2)*dp[l];
			else if(k==l)dp[i]+=C(dp[k],2)*dp[j];
			else dp[i]+=dp[j]*dp[k]*dp[l];
		}
	}
}
  1. 加强版\(O(n^2)\)

首先显然我们之前枚举了太多我们用不到的选项和重复选项。而且重复这块很致命。考虑改变状态设计。

\(dp[k][i][j]\)\(i\)个碳原子有\(j\)个子树的时候的方案数。那么我们可以分别枚举当前子树大小\(i\),母树(就是子树的父亲为根的树)大小\(j\),子树数量\(k\)和与当前子树大小相同的子树个数\(l\),这样我们就可以以一个稍微大一点点的常数和少一个\(n\)的复杂度逐步转移状态,同时少了很多重复。(\(dp\)能优化的大概到此为止了)。同时我们倒序枚举\(j\),这样第一维可以压掉。

int main(){
	scanf("%d",&n);
	dp[1][0]=1;
	for(int i=1;i<n;i++){//子树大小 显然不能到n 
		for(int j=n;j>i;j--){//父亲大小 显然最小不能到i 
			for(int k=1;k<=3;k++){//有几棵子树 
				for(int l=1;l<=k&&l*i<j;l++){//有几棵和i长的一样的子树 
					dp[j][k]=dp[j][k]+1ll*C(dp[i][0]+dp[i][1]+dp[i][2],l)*dp[j-i*l][k-l];
				}
			}
		}
	}
	printf("%d",dp[n][4]);
}
  1. 加强版加强版\(O(n\log n)\)

Polya定理。先咕着。不知道什么时候能补。也许AFO了都补不上。

首先既然要Polya定理那就先找置换。发现因为有\(3\)个子树所以有\(A_3^3=6\)个置换,就是\(3\)的全排列。(置换不懂的先去oiwiki看看群论,然后这里的置换就是三棵子树之间互相置换,这里是从\((1,2,3)\)置换到下面的)。这里枚举出来:

首先设答案的生成函数为\(F(x)\),我们要求第\(n\)\(a_n\)就是答案。那么:

对于置换\((1,2,3)\),三个自环,互不影响,直接三次方卷上。

对于置换\((1,3,2),(2,1,3),(3,2,1)\),两个环,一个一元一个二元。所以是\(3F(x)F(x^2)\)

剩下的置换是三元环,也就是\(2F(x^3)\)

于是按照polya定理的柿子写出来搞个生成函数形式,就变成了这个诡异的方程:

\[F(x)=1+x\frac {F(x)^3+3F(x)F(x^2)+2F(x^3)}6 \]

我管你会不会解反正我不会解

好了以上是三个月前naive的我(虽然现在也一样naive)的吐槽。我们其实可以把上边那个 \(2\sqrt 2\) 东西牛顿迭代求,对着 \(F(x)\) 求个偏导就行了。代码不想写了,有点工业。

posted @ 2022-10-10 17:23  gtm1514  阅读(69)  评论(0编辑  收藏  举报