CF674E Bear and Destroying Subtrees
题目大意
一棵树。初始时只有一个节点:根节点 (1)。
你需要支持 (q) 次操作。操作可能是如下两种之一:
- (1 v)。表示添加一个叶子,它的父亲是 (v)。
- (2 v)。表示对以 (v) 为根的子树进行询问。你需要回答:如果子树里每条边以 (frac{1}{2}) 的概率保留(不同边被保留的概率相互独立),这棵子树的期望最大深度是多少?深度定义为到根(子树的根,即 (v))路径上的边数,这些边必须全部被保留。
数据范围:(1leq qleq 5 imes 10^5)。
本题题解
先不考虑动态添加叶子。假设已知某棵树的形态,如何回答单次询问?根据期望的定义,可以写出答案为:
其中 (P(dots)) 表示一件事发生的概率。
要让树的最大深度 (geq i),只需要存在任意一个深度为 (i) 的点,使得它到根路径上的边全部被保留即可。这样的情况可能有很多种。具体来说,设树上深度为 (i) 的点有 (k) 个,那它们的任何一个非空子集满足要求,都是合法的情况。所以朴素的想法是,对 (2^k - 1) 种情况做容斥。但这显然太慢了!
既然 (geq i) 的概率不好求,那么考虑补集转化,求最大深度 $ < i$ 的概率。此时的要求是:所有深度为 (i) 的点,到根路径上的边,都不能被全部保留。换句话说,我们把“(exist)”的问题,转化为了“(forall)”的问题。这就好办多了。考虑树形 DP。设 (f(u, i)) 表示 (u) 的子树,最大深度(定义为到 (u) 的距离,而不是到整棵树的根)$ < i$ 的概率。则有转移:
其中括号里的 (1) 表示 ((u, v)) 这条边不保留时的概率,(f(v, i - 1)) 表示保留时的概率。边界是 (f(u, 0) = 0)。
答案就是:
设树的节点数为 (n)。则按此方法计算答案的时间复杂度是 (mathcal{O}(n^2)),更准确地说是 (mathcal{O}(ncdot mathrm{maxdep}))。
考虑动态添加叶子。注意到只需要更新它所有祖先的 DP 值。并且距离它为 (l) 的祖先 (a),只有 (f(a, l)) 的值会受到影响,所以添加一个叶子的时间复杂度是 (mathcal{O}(mathrm{maxdep})) 的。
继续优化,发现当 (i) 较大时,最大深度等于 (i) 的概率较低。也就是说,存在一个阈值 (d),满足当 (i > d) 时,(f(1, i + 1) - f(1, i) < mathrm{eps} = 10^{-6})。此时就没必要计算 (f(u, i + 1)) 了。可以证明这个阈值大约在 (60) 左右。所以每次添加叶子,只需要向上修改 (d = 60) 个祖先的 DP 值。于是我们在 (mathcal{O}(nd)) 的时间复杂度内解决了本题。
参考代码
核心片段:
const int MAXN = 5e5;
const int D = 60;
int q;
int n, fa[MAXN + 5];
double dp[MAXN + 5][D + 5];
void update(int v) {
static int anc[D + 5];
int top = 0;
int u = v;
for (int i = 0; i <= D; ++i) {
if (u == 0) break;
top = i;
anc[i] = u;
u = fa[u];
}
for (int i = top; i >= 2; --i) {
int u = anc[i];
int son = anc[i - 1];
dp[u][i] /= 0.5 * (1 + dp[son][i - 1]); // 把老的 dp[son] 值对父亲的贡献去掉
}
for (int i = 1; i <= D; ++i) dp[v][i] = 1;
for (int i = 1; i <= top; ++i) {
int u = anc[i];
int son = anc[i - 1];
dp[u][i] *= 0.5 * (1 + dp[son][i - 1]);
}
}
double query(int v) {
double res = 0;
for (int i = 1; i < D; ++i) {
res += i * (dp[v][i + 1] - dp[v][i]);
}
return res;
}
int main() {
n = 1;
for (int i = 1; i <= D; ++i) dp[1][i] = 1;
read(q);
for (int tq = 1; tq <= q; ++tq) {
int op, v;
read(op); read(v);
if (op == 1) {
++n;
fa[n] = v;
update(n);
} else {
print(query(v));
}
}
return 0;
}