嘘~ 正在从服务器偷取页面 . . .

LOJ6269 烷基计数 加强版 题解


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


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录