1 条题解
-
0
题目简述
给定一个仙人掌图(每个点最多属于一个简单环),求随机选择一个点集 ,其生成的斯坦纳树(Steiner Tree)边数的期望值。 由于总方案数是 ,我们需要计算所有可能的点集 对应的斯坦纳树边数之和,最后除以 (模意义下乘逆元)。
核心思路:边的贡献 + 环的处理
根据期望的线性性,总期望等于每条边在斯坦纳树中出现的概率之和。 也就是说,我们不需要枚举集合 ,而是计算每条边被“选中”的次数。
对于图中的边,分为两种情况:树边(桥) 和 环上的边。
1. 树边(桥)的处理
如果一条边 是桥,去掉它会将图分成两个连通块,大小分别为 和 。 这条边被选入斯坦纳树,当且仅当:点集 在这两个连通块中都至少选了一个点。
- 连通块 1 选点的方案数:(排除全不选)。
- 连通块 2 选点的方案数:。
- 贡献:。
这一步在代码的
dfs1末尾处理。2. 环的处理
对于环上的边,不能简单地判断断开后的连通性。仙人掌图的性质允许我们单独处理每个环。 对于一个长度为 的环,假设环上挂着的子树大小分别为 。 如果在环上(包括挂着的子树)选择了一些点,这些点在环上的分布将决定使用了多少环上的边。
结论:在环上,斯坦纳树的边数 = 环长 - 环上被选点分割出的最大间隙(Max Gap)。
- 如果没有选点,边数为 0。
- 如果选了点,这些点把环分成了若干段“空白边”,其中最长的一段空白边不会被包含在斯坦纳树中,其余边都会被包含。
因此,我们需要计算所有选点方案下 的总和。
代码详解
变量定义
ans:累加的总答案(所有方案的边数之和)。pw2[]: 的幂次预处理。e[]:邻接表存图。siz[]:DFS 序中的子树大小。a[]:当前找到的环上的节点列表。b[]:环上节点 挂着的子树大小(不包含环上其他部分)。
函数解析
1.
dfs1(u, fa):找环与处理树边 这是主 DFS 函数。- 它计算子树大小
siz。 - 判环:如果遇到
v已经被访问过且v != fa,说明找到了一个环(v是环的“根”)。- 利用
far数组(记录父节点)回溯,将环上的点存入a[]。 - 调用
dfs2计算挂在环上每个点的子树大小,存入b[]。 - 调用
sol()处理这个环的贡献。
- 利用
- 处理树边:如果
!u[tag](当前边不在环上),则按照树边的公式计算贡献:int x = u[siz]; int y = n - u[siz]; ans = (ans + (lnt)(pw2[x] - 1) * (pw2[y] - 1)) % M;
2.
sol():环上的 DP 这是最复杂的部分,用于计算环的贡献。 问题转化为:在一个圆环上,每个点 有 种被选中的权重(如果不选该点子树内的点,则视为该点未被选中)。求所有方案的 之和。- 枚举起点 :为了打破环的对称性,强制规定环上的点 是第一个被选中的点(即 被选中,且 之前的若干点构成回绕的 Gap)。
- DP 状态:
f[i][j]表示:- 当前考虑到环上的第 个点(相对于起点 )。
- 点 被选中(其子树内有选点)。
- 从起点 到点 这段链上,相邻选中点之间的最大间隙是 。
- 转移:
我们需要枚举上一个选中点 ()。
新产生的间隙是 。
新的最大间隙是 。
为了优化复杂度,代码中使用
g[j]数组维护了前缀和/辅助转移。 - 计算贡献:
当确定起点是 ,终点是 时,链上的最大间隙是 。
此时还有一个回绕间隙(从 绕回 ),长度为 。
整个环的最大间隙为 。
贡献累加:
这里ans = (ans + (lnt)(t - max(t + s - i, j)) * f[i][j]) % M;t - max(...)就是该方案下斯坦纳树在环上的边数。
3.
dfs2简单的辅助函数,用于在发现环之后,重新遍历环上节点挂着的子树,计算b[i](即不通过环上边能到达的节点数量)。复杂度分析
- 。
- 树边处理是 。
- 环处理 DP:
- 外层枚举起点 :。
- 内层 DP 状态 :。
- 总计 。
- 由于 ,最坏情况下也就是一个大环 。
- 对于 ,运算量在 级别,非常轻松通过。
总结
代码通过 Tarjan 类似的思路找环(利用 DFS 树的返祖边),将仙人掌图拆解为树边和简单环。树边直接用组合数学计算,简单环则通过枚举起点的区间 DP()来统计斯坦纳树的边数贡献。最后乘上 得到期望。
- 1
信息
- ID
- 5999
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者