LOJ #6669,Nauuo and Binary Tree
神奇交互题。原题
题意大概就是,有一棵 \(n\) 个结点的 二叉树,我们并不知道这棵树的具体形态,只知道一个 \(n\) 和知道它的根是 \(1\)。每次可以询问树上两个点的距离,最后问你这棵树的形态,即输出每个点的父亲。
\(n \le 3000\) ,询问次数少于 \(30000\) 。
首先显然可以通过询问每个点到 \(1\) 的距离得到每个点的深度。那么感性一下,可以一层一层的处理,这样应该比较方便。
接下来是听课的时候听到的,被秀了一脸,就直接上做法再解释正确性了。完全不知道是怎么想到的。
给每个点维护一个 \(bot\) 值表示其所在重链的底部。\(n \le 3000\) ,这个可以直接每次大力更新。(维护 \(size,son\) )
令目前所询问的点为 \(\alpha\)
\(now\)表示当前点,然后每次从跟开始跳。跳的时候,询问 \(\alpha\) 跟 \(bot[now]\) 的距离,从而算出其 \(\text{lca}\) 深度。
那么从 \(now\) 开始跳重儿子直到来到 \(\text{lca}\) 处。此时,\(\alpha\) 一定在 \(now\) 的轻儿子那棵子树,跳过去就好。
循环直至此时 \(now\) 已经没有轻儿子。此时,由于我们是按深度逐层处理,\(\alpha\) 就是那棵轻儿子所在子树的根,也就是那个轻儿子。
连上边,一路上跳更新树链剖分情况。
注意到每次询问后要么结束,要么经过一条轻边。从任意一点到根所经过的轻边,不会超过 \(\log n\) 条。
也就是说,询问次数的数量级是 \(n \log n\) 的,并且完全跑不满。非常优秀的解决了。
代码:
#include <cstdio>
#include <vector>
const int maxn = 3e3 + 5;
inline int El_Psy_Congroo(int u,int v){
printf("? %d %d\n",u,v);fflush(stdout);
int dis ;scanf("%d",&dis) ;return dis ;
}
int n,dep[maxn],size[maxn],son[maxn],bot[maxn];
std::vector<int> Set[maxn];int answer[maxn];std::vector<int> e[maxn];
int until(int start,int goal){
int now = start;
while(dep[now] != goal) now = son[now];
return now ;
}
inline int light_son(int now){
if(e[now].size() <= 1) return 0;
if(e[now].front() != son[now]) return e[now].front();return e[now].back();
}
void update(int fa,int now){
answer[now] = fa;e[fa].push_back(now);
size[now] = 1;bot[now] = now;
if(son[fa] == 0) son[fa] = now,bot[fa] = now;
while(fa){
size[fa] ++;
if(size[light_son(fa)]>size[son[fa]])
son[fa] = light_son(fa);
bot[fa] = bot[son[fa]],fa = answer[fa];
}
}
int main(){
scanf("%d",&n);dep[1] = 0 , size[1] = 1 , bot[1] = 1;
for(int i=2;i<=n;i++) dep[i] = El_Psy_Congroo(1,i),Set[dep[i]].push_back(i);
for(int i=1;i<=n;i++){
if(Set[i].empty()) continue;
for(int j=0;j<(int)Set[i].size();j++){
int now = 1;
while(true){
int dis = El_Psy_Congroo(Set[i][j],bot[now]);
int pos = (i+dep[bot[now]]-dis) >> 1;
now = until(now,pos);
if(light_son(now)) now = light_son(now);else break;
}
//dis(u,bot[now]) = i + dep[bot[now]] - 2*dep[lca(u,bot[now])]
update(now,Set[i][j]);
}
}
printf("! ");
for(int i=2;i<=n;i++) printf("%d%c",answer[i]," \n"[i==n]);fflush(stdout);
return 0 ;
}
$$
\texttt{El Psy Kongroo}
$$