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

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\) 个,可重复)

则完整转移方程为 \[ f_{s,i,j} = \sum_{0 \le k \le j \land sk < i} f_{s-1,i-sk,j-k} \times\dbinom{c+k-1}{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 !
评论
  目录