CF674E Bear and Destroying Subtrees

CF674E Bear and Destroying Subtrees

题目大意

题目链接

一棵树。初始时只有一个节点:根节点 \(1\)

你需要支持 \(q\) 次操作。操作可能是如下两种之一:

  • \(1\ v\)。表示添加一个叶子,它的父亲是 \(v\)
  • \(2\ v\)。表示对以 \(v\) 为根的子树进行询问。你需要回答:如果子树里每条边以 \(\frac{1}{2}\) 的概率保留(不同边被保留的概率相互独立),这棵子树的期望最大深度是多少?深度定义为到根(子树的根,即 \(v\))路径上的边数,这些边必须全部被保留。

数据范围:\(1\leq q\leq 5\times 10^5\)

本题题解

先不考虑动态添加叶子。假设已知某棵树的形态,如何回答单次询问?根据期望的定义,可以写出答案为:

\[\begin{align} &\sum_{i = 1}^{\mathrm{maxdep}} i\cdot P(\text{树的最大深度为 $i$})\\ = &\sum_{i = 1}^{\mathrm{maxdep}} P(\text{树的最大深度为 $\geq i$}) \end{align} \]

其中 \(P(\dots)\) 表示一件事发生的概率。

要让树的最大深度 \(\geq i\),只需要存在任意一个深度为 \(i\) 的点,使得它到根路径上的边全部被保留即可。这样的情况可能有很多种。具体来说,设树上深度为 \(i\) 的点有 \(k\) 个,那它们的任何一个非空子集满足要求,都是合法的情况。所以朴素的想法是,对 \(2^k - 1\) 种情况做容斥。但这显然太慢了!

既然 \(\geq i\) 的概率不好求,那么考虑补集转化,求最大深度 $ < i$ 的概率。此时的要求是:所有深度为 \(i\) 的点,到根路径上的边,都不能被全部保留。换句话说,我们把“\(\exist\)”的问题,转化为了“\(\forall\)”的问题。这就好办多了。考虑树形 DP。设 \(f(u, i)\) 表示 \(u\) 的子树,最大深度(定义为到 \(u\) 的距离,而不是到整棵树的根)$ < i$ 的概率。则有转移:

\[f(u, i) = \prod_{v\in\mathrm{son}(u)} \frac{1}{2}(1 + f(v, i - 1)) \quad (i > 0) \]

其中括号里的 \(1\) 表示 \((u, v)\) 这条边不保留时的概率,\(f(v, i - 1)\) 表示保留时的概率。边界是 \(f(u, 0) = 0\)

答案就是:

\[\sum_{i = 1}^{\mathrm{maxdep}}i\cdot (f(1, i + 1) - f(1, i)) \]

设树的节点数为 \(n\)。则按此方法计算答案的时间复杂度是 \(\mathcal{O}(n^2)\),更准确地说是 \(\mathcal{O}(n\cdot \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;
}
posted @ 2021-01-22 22:01  duyiblue  阅读(111)  评论(0编辑  收藏  举报