LOJ6269 烷基计数 加强版 题解
题目链接:#6269. 烷基计数 加强版
题意:众所周知,大连 24 中是一所神奇的学校,在那里,化竞的同学很多都擅长写代码。
有一天,化学不及格的胡小兔向化竞巨佬晴岚请教化学题:
“$n$ 个碳原子的烷基共有多少种同分异构体?”
刚刚得了化竞全市第一的晴岚听了,认为这道题十分简单,建议胡小兔写个程序解决这个问题。但胡小兔弱得连什么是同分异构体都不知道,于是晴岚给胡小兔画了个图——例如 $n=4$ 时 (即丁基),有 $4$ 种同分异构体:
同理,其他常见烷基同分异构体数目如下表:
n 1 2 3 4 5 6 同分异构体数目 1 1 2 4 8 17 现在已知碳原子个数 $n$,求对应的烷基有多少种同分异构体。
答案对 $10^9 + 7$ 取模。
数据范围: $n \le 5\times 10^3$
注意:这里的烷基计数不用考虑空间异构,能否稳定存在等各种特殊情况。也就是说,你要求的是 $n$ 个点的每个点度数不超过 $4$ 且根的度数不超过 $3$ 的有根树的数目。
据说这种叫:无标号树计数题
最优的解法是 $O(n \log^2 n)$ 的,涉及Pólya原理和生成函数,这里暂且不提。
首先考虑弱化版 #6185. 烷基计数 的dp ( $n \le 400$ )
设 $f_i$ 表示 $i$ -烷基的个数,然后枚举三个子树的大小,乘个组合数就好了
实际上因为 $s_1+s_2+s_3=i-1$ ,因此只用枚举两个子树的大小
时间复杂度 $O(n^3)$ ,可以过弱化版。
考虑这道题怎么做
刚刚的dp瓶颈在于枚举了所有的子树大小
还记得树上背包吗?我们也有很多子树,但是我们是一个个子树添加的,而不是枚举每个子树的大小。
因此可以尝试像树上背包一样,一个个子树的添加
可是这道题的子结点没有区别,因此我们按子树大小顺序一一添加
枚举一个 $\mathtt{sz}$ (从 $1$ 到 $n$ ) ,然后去对所有的dp值产生贡献
或者等价地,在dp中加上一维 $s$ 表示当前允许的子树大小
同时我们还要记录当前根节点的度数
设 $f_{s,i,j}$ 表示 $i$ -烷基,根节点度数为 $j$ ,所有子树的大小不超过 $s$ 的方案数
考虑转移。首先,如果所有子树的大小都小于 $s$ ,则答案为 $f_{s-1,i,j}$
否则设存在 $k$ 个子树的大小为 $s$ ,显然 $1\le k \le \min\left\{j,\left\lfloor{\frac{i-1}{s}}\right\rfloor\right\}$ ,
把这些子树去掉以后,剩下 $i-sk$ 个点,根节点的度数为 $j-k$ ,且剩余子树大小均不超过 $s-1$
对应到状态就是 $f_{s-1,i-sk,j-k}$
然后就是考虑这 $k$ 个子树的分布,或者说把这 $k$ 个子树插进去
设大小为 $s$ 的子树有 $c(c=\sum f_{s-1,s,j})$ 个(显然这 $c$ 个本质不同)
那么这就是一个可重组合,方案数为 $\dbinom{c+k-1}{k}$ ( $c$ 个选 $k$ 个,可重复)
则完整转移方程为
时间复杂度 $O(n^2m \log m)$ ,其中 $m$ 为度数的限制,即 $4$ 。
然后这个第一维显然可以滚动数组。故空间复杂度为 $O(nm)$
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N ((int)(5e3+15))
#define M 4
const int mod=1e9+7,inv6=(mod+1)/6;
int n,f[N][M];
void add(int &x,int y){ (x+=y) >= mod? x-=mod : 0;}
int C(int n,int m)
{
switch(m)
{
case 0: return 1;
case 1: return n % mod;
case 2: return n*(n-1)/2 % mod;
default: return n*(n-1) % mod * (n-2) % mod * inv6 % mod;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
f[1][0]=1;
for(int sz=1,cur; sz<n; ++sz)
{
cur = 0; for(int i=0; i<M; i++) add(cur,f[sz][i]);
for(int i=n; i>=1; i--)
for(int j=1; j<M; j++)
for(int k=1; k<=j && sz*k<i; k++)
add(f[i][j],f[i-sz*k][j-k]*C(cur+k-1,k)%mod);
}
int res=0; for(int i=0; i<M; i++) add(res,f[n][i]);
cout << res << '\n';
return 0;
}
参考文献:
[1] https://yhx-12243.github.io/OI-transit/records/loj6185%3Bloj6269.html