LOJ6269 烷基计数 加强版 题解
题目链接:#6269. 烷基计数 加强版
题意:众所周知,大连 24 中是一所神奇的学校,在那里,化竞的同学很多都擅长写代码。
有一天,化学不及格的胡小兔向化竞巨佬晴岚请教化学题:
“n 个碳原子的烷基共有多少种同分异构体?”
刚刚得了化竞全市第一的晴岚听了,认为这道题十分简单,建议胡小兔写个程序解决这个问题。但胡小兔弱得连什么是同分异构体都不知道,于是晴岚给胡小兔画了个图——例如 n=4 时 (即丁基),有 4 种同分异构体:
同理,其他常见烷基同分异构体数目如下表:
n 1 2 3 4 5 6 同分异构体数目 1 1 2 4 8 17 现在已知碳原子个数 n,求对应的烷基有多少种同分异构体。
答案对 109+7 取模。
数据范围: n≤5×103
注意:这里的烷基计数不用考虑空间异构,能否稳定存在等各种特殊情况。也就是说,你要求的是 n 个点的每个点度数不超过 4 且根的度数不超过 3 的有根树的数目。
据说这种叫:无标号树计数题
最优的解法是 O(nlog2n) 的,涉及Pólya原理和生成函数,这里暂且不提。
首先考虑弱化版 #6185. 烷基计数 的dp ( n≤400 )
设 fi 表示 i -烷基的个数,然后枚举三个子树的大小,乘个组合数就好了
实际上因为 s1+s2+s3=i−1 ,因此只用枚举两个子树的大小
时间复杂度 O(n3) ,可以过弱化版。
考虑这道题怎么做
刚刚的dp瓶颈在于枚举了所有的子树大小
还记得树上背包吗?我们也有很多子树,但是我们是一个个子树添加的,而不是枚举每个子树的大小。
因此可以尝试像树上背包一样,一个个子树的添加
可是这道题的子结点没有区别,因此我们按子树大小顺序一一添加
枚举一个 sz (从 1 到 n ) ,然后去对所有的dp值产生贡献
或者等价地,在dp中加上一维 s 表示当前允许的子树大小
同时我们还要记录当前根节点的度数
设 fs,i,j 表示 i -烷基,根节点度数为 j ,所有子树的大小不超过 s 的方案数
考虑转移。首先,如果所有子树的大小都小于 s ,则答案为 fs−1,i,j
否则设存在 k 个子树的大小为 s ,显然 1≤k≤min{j,⌊i−1s⌋} ,
把这些子树去掉以后,剩下 i−sk 个点,根节点的度数为 j−k ,且剩余子树大小均不超过 s−1
对应到状态就是 fs−1,i−sk,j−k
然后就是考虑这 k 个子树的分布,或者说把这 k 个子树插进去
设大小为 s 的子树有 c(c=∑fs−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