CF1534H Lost Nodes 题解
题面
这是一道交互题。
给你一棵包含 \(n\) 个节点的树。你需要猜测出这棵树上的两个特殊节点 \(a,b\)(\(a\) 不一定与 \(b\) 不同)。
首先,你会被告知一个整数 \(f\),满足节点 \(f\) 是节点 \(a,b\) 之间的简单路径上的一点。
接下来你可以进行询问,具体的,每次给定整数 \(r\),交互器会给你当树的根节点为 \(r\) 时,节点 \(a,b\) 的最近公共祖先是哪个节点。
特别的,在给定树的形态之后,你需要先给出在所有 \(a,b,f\) 的可能的取值中,至少需要多少次询问才能保证得出特殊节点的编号。
\(1\leq n\leq10^5;1\leq a,b,f\leq n\) 且节点 \(f\) 在节点 \(a,b\) 之间的简单路径上(包括节点 \(a,b\))。
交互格式如下:
首先输入整数 \(n\),表示树的节点数。接下来 \(n-1\) 行,每行两个整数 \(u,v(1\leq u,v\leq n,u\not=v)\) 表示节点 \(u,v\) 之间有一条边。我们保证给定的是一棵树。
接下来,你需要先输出对于这棵树,确定 \(a,b\) 所需要的最小询问次数,我们保证这个最小询问次数 \(\in[0,n]\),接下来你的询问次数将被限制在你所输出的这个最大询问次数内。
接下来输入 \(f\)。之后你便可以开始询问,询问格式为? r
,你需要满足 \(1\leq r\leq n\)。如果你认为你得到了答案,按照! a b
的格式输出,\(a,b\) 的顺序随意。
答案错误、程序所输出的询问次数过多、询问不合法均会导致Wrong Answer
的评测结果。同时,请注意刷新缓冲区。
题解
动态规划
交互
换根 DP
为什么一篇题解是 Treap 换根,然后网上大部分题解都在 Treap 啊?
感觉很多题解没有将清楚结论怎么来的就开始 DP 了。
之后我们假设以给定的 \(f\) 为根。
同时我们可以发现,假设询问是以 \(t\) 为根,得到 \(a, b\) 的 LCA 为 \(v\),那么相当于告诉我们 \(a, b\) 路径上离 \(t\) 更近的点是 \(v\),这个可以减少被树绕晕的情况,方便发现性质。
重要的性质
在以 \(f\) 为根的情况下,我们每次询问叶子肯定比问非叶子更优(或者说获得的信息量更多)。
感觉很多题解都没有讲为什么。
假设我们问 \(x\) 子树的一个叶子 \(y\),那么我们只要说明问 \(y\) 获得的信息量多于问 \(x\) 即可。
情况 | 问 \(x\) | 问 \(y\) |
---|---|---|
\((a, b)\) 一个在 \(x\) 的子树内,一个不在 | 返回 \(x\),只会告诉我们 \(a, b\) 中有一个点在 \(x\) 子树内 | 假设在 \(x\) 子树里的是 \(a\),那么会返回 \(lca(y, a)\),那么有更大概率帮助我们确定 \(a\) 的进一步位置 |
\((a, b)\) 两个都不在 \(x\) 的子树内 | 返回 \(f\) | 返回 \(f\) |
注意,因为题目保证,刚开始的时候情况不可能是两个都在 \(x\) 子树内(最开始在草稿纸上推的时候推错了)
交互流程
假设先不要最小化步数,那么我们可以定义 \(\tt solve(x)\) 表示当前我们确定了有一个节点在 \(x\) 子树内:
- 先随便
rand
一个顺序遍历儿子,每次 ask 一个儿子的叶子; - 如果 ask 的结果为 \(y \neq x\),那么就确定了该节点在其子树的位置,那么直接返回 \(\tt solve(y)\);
- 否则继续遍历儿子。
对于根,也是类似的,还是遍历儿子,找到两个点然后 \(\tt solve\) 即可。
最小化次数
现在,我们要求最小化步数,那么我们可以记一个状态 \(f_x\) 表示我们已知有一个节点在 \(x\) 子树内的最坏情况下的询问次数,对于叶子 \(f_x = 1\)。
至于转移,随便一个 \(son_x\) 的排列,然后按照这个顺序去遍历 \(y \in son_x\) 即可,记 \(\tt rank(y) \in [0, |son_x| - 1]\) 表示 \(y\) 是第 \(\tt rank(y)\) 个遍历到的。
然后有:
这个是因为一旦询问到一个点我们就可以确定这个点的进一步子树位置了,那么就递归到另外一个子问题了,次数为该子问题的最坏次数 + 前面浪费的次数。
显然,\(son_x\) 的最佳排列,就是按照 \(f_y\) 从大到小排序。
对于根,我们的 \(ans_x\) 需要特殊处理,那么:
肉眼观察可以发现,\(y_1\) 应该取 \(son_x\) 第一个,然后后面可以遍历得到。
于是暴力就写完了。
换根 DP
因为我们是定根了,但是要求出每个 \(f\) 的答案。
然后魔幻的来了,网上现有的题解全在 Treap!
其实就是一个比较基础的前后缀合并就可以完成的内容,应该不至于用 Treap,虽然复杂度都是 \(\mathcal O(n\log n)\) 的。
具体可以看代码。