计算烷烃的同分异构体个数 (结构异构)
本质上就是一个无标号无根树带度数限制的计数问题.
将问题一般化:
求 n 个点的无标号无根树的个数, 且每个点的度数不超过 m.
烷基 (有根树)
首先考虑计算烷基的个数 (即有根树).
考虑暴力 DP. 设状态为 $f(i,j,k)$, 表示当前共有 $i$个点, 最大的子树大小为 $j$, 且根的度数为 $k$. 对于状态 $f(i,j,k)$, 通过枚举最大子树的个数 l 和次大子树的大小 size, 有
$f(i,j,k)=∑_{l}∑_{s}+f(i−jl,size,k−l)\left\{ \begin{matrix}s+l-1\\l \end{matrix} \right\}$
其中 $s=∑^{j}_{a=1}∑^{m−1}_{b=0}f(j,a,b)$, 组合数是用来可重集计数的.
这是$ O(n^3m^2) $的. 显然可以前缀和优化, 但是空间撑不住. 还可以做得更好.
考虑如何省掉 $j$ 这一维状态. 即设状态为 $f(i,j)$, 表示当前共有 $i$ 个点, 根的度数为 $k$.
考虑 DP 的一个技巧: 强行规定转移顺序. 即, 先 $1. . .n$ 枚举 size, 表示强制用最大子树大小为 size 的情形来转移. 不妨设
$ s=∑^{m−1}_{k=0}f(size,k)$,
那么对于一个 $f(i,j)$, 再枚举一个最大子树 (即子树大小为 size 的子树) 的个数 k, 我们便有转移
$f(i,j)←f(i,j)+f(i−size×k,j−k)\left\{ \begin{matrix}s+k-1\\k \end{matrix} \right\}$
这是 $O(n^2m^2)$ 的. 如果是算烷基的话, 便是 $O(n^2)$ 的.
烷烃 (无根树)
本来我想枚举主链长度来做的, 后来发现直接利用树的重心来做非常方便.
首先只要某个点 $u$ 满足其子树大小都 $≤n2$, 那么这个点是这颗树的重心. 比较显然的是, 重心最多只会有两个, 并且有两个重心的情形, 两个重心一定相邻, 并且另一个重心做根的时候, 这个重心的子树大小为 $n2$. (当然 $n$ 必须要是偶数)
然后很多无根树同构的问题就可以通过重心转化为有根树同构. 烷烃就可以这么计数. 因为我们可以在 DP 烷基的时候, 强制$ size<n2 $(注意是小于), 这样求出的 $f(i,j)$ 就是点数为 $i$ 且重心度数为 $j$ 的无根树个数. 那么答案为:
$\sum^m_{k=0}f(n,k)+[n\pmod{2}=0] \left\{ \begin{matrix}\sum^{m−1}_{k=0}f(\frac{n}{2},k)+1\\2 \end{matrix} \right\} $
前一项为一个重心的情形, 后一项为两个重心的情形.
代码附上
def C(n, k): ret = 1 for i in range(k): ret *= n - i for i in range(1, k + 1): ret //= i return ret def calc(n, m): dp = [[0 for i in range(m + 1)] for i in range(n + 1)] dp[1][0] = 1 for size in range(1, (n - 1) // 2 + 1): s = sum(dp[size][:-1]) for i in range(n, size, -1): for j in range(1, m + 1): for k in range(1, j + 1): if size * k < i: dp[i][j] += dp[i - size * k][j - k] * C(s + k - 1, k) ret = sum(dp[n]) if n % 2 == 0: ret += C(sum(dp[n // 2][:-1]) + 1, 2) return ret n = int(input()) print(calc(n, 4))
出处:
计算烷烃的同分异构体个数(结构异构) 【J.O】_Debug
链接:http://debug18.com/posts/calculate-the-number-of-structural-isomers-for-alkanes/
posted on 2018-08-06 21:20 legend_noa 阅读(3956) 评论(0) 编辑 收藏 举报