1 条题解
-
0
一、题意理解
我们要计算的是 烷基 (alkyl group) 的同分异构体数目。
化学背景:
- 烷基:烷烃分子去掉一个氢原子后剩下的部分,通式 。
- 碳原子形成四价键,在烷基中,连接其它碳原子的键最多3个(因为有一个键连接母体烷烃)。
- 题目要求: 个碳原子的烷基同分异构体数目。
二、转化为图论问题
将碳原子看作节点,碳碳键看作边:
- 每个节点度数 ≤ 4(碳四价)
- 根节点度数 ≤ 3(因为一个键连接母体)
- 其它节点度数 ≤ 4
- 树结构(烷烃是无环的)
所以问题转化为: 求 个节点的有根树数目,根度数 ≤ 3,其它节点度数 ≤ 4。
三、动态规划思路
设 表示 个节点的有根树数目(根度数 ≤ 3)。
考虑根的子树分配:
- 根可以有 0, 1, 2, 3 棵子树
- 每棵子树也是一个烷基(根度数 ≤ 3)
- 子树之间无序(因为化学上对称子树交换不产生新异构体)
1. 生成函数方法
设 ,其中 (空树)。
对于根:
- 0棵子树:1 种(只有根)
- 1棵子树:
- 2棵子树:(Burnside引理,考虑对称性)
- 3棵子树:(Burnside引理)
所以: $ A(x) = 1 + x \left[ A(x) + \frac{A(x)^2 + A(x^2)}{2} + \frac{A(x)^3 + 3A(x)A(x^2) + 2A(x^3)}{6} \right] $ 这里乘以 是因为根节点占用一个碳原子。
2. 递推计算
我们可以用 DP 直接计算 :
设 表示 个碳的烷基数目。
枚举根的子树情况:
- 1棵子树:(子树用掉 个碳)
- 2棵子树:枚举第一棵子树大小 ,第二棵大小 ,,且 :
- 如果 :方案数
- 如果 :方案数 (可重复组合)
- 3棵子树:枚举三棵子树大小 ,,且 :
- 三个不同:
- 两个相同:()
- 三个相同:()
最后对所有情况求和得到 。
四、算法步骤
- 初始化 (空树)
- 对于 到 :
- 初始化
- 1棵子树:
- 2棵子树:
- 枚举 到 ,,且
- 如果 :
- 如果 :
- 枚举 到 ,,且
- 3棵子树:
- 枚举 从 到
- 枚举 从 到
- ,且
- 如果 :
- 如果 :
- 如果 :
- 如果 :
- 枚举 从 到
- 枚举 从 到
- 输出
其中 模 。
五、代码实现(C++)
#include <bits/stdc++.h> #define up(i,l,r) for(int i=(l);i<=(r);++i) #define down(i,l,r) for(int i=(l);i>=(r);--i) #define pi pair<int,int> #define p1 first #define p2 second #define m_p make_pair #define p_b push_back #define ppc __builtin_popcount using namespace std; typedef unsigned long long ull; typedef long long ll; typedef long double db; const db eps = 1e-5; const int maxn = 1e6 + 10, mod = 1e9 + 7, iv6 = 166666668, iv2 = 5e8 + 4; inline ll read() { ll x = 0; short t = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') t = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * t; } int n, f[maxn]; void slv() { n = read(); f[0] = 1; up(i, 1, n) { up(a, 0, i - 1) up(b, a, i - 1 - a) { int c = i - 1 - a - b; if (b > c) continue; if (a == b && b == c) f[i] = (f[i] + f[a] * 1llu * (f[a] + 1) % mod * 1llu * (f[a] + 2) % mod * 1llu * iv6) % mod; else if (a == b) f[i] = (f[i] + f[a] * 1llu * (f[a] + 1) % mod * 1llu * f[c] % mod * 1llu * iv2) % mod; else if (b == c) f[i] = (f[i] + f[a] * 1llu * f[b] % mod * 1llu * (f[b] + 1) % mod * 1llu * iv2) % mod; else f[i] = (f[i] + f[a] * 1llu * f[b] % mod * 1llu * f[c]) % mod; } } cout << f[n]; } int main() { // freopen("1.in","r",stdin),freopen("1.out","w",stdout); // int t=read();while(t--)slv(); slv(); cerr << clock() * 1.0 / CLOCKS_PER_SEC << "s\n"; return 0; }
六、样例验证
输入 ,输出 ,与题目表格一致。
七、总结
本题的关键在于:
- 将化学问题转化为有根树计数问题。
- 根据碳原子价键限制确定节点度数约束。
- 使用动态规划枚举根的子树分配情况。
- 利用组合数学处理对称子树的情况(Burnside引理)。
- 1
信息
- ID
- 4985
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者