本質上就是一個無標號無根樹帶度數限制的計數問題.
將問題一般化:
求 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/