说在前面的话
这是一道很有意思的题,巧妙利用了树剖的性质,出题人有点东西的。
题解
这道题我们需要利用轻重链剖分的性质,做到一次塞点复杂度只有 (O(log_2n)) 。
首先比较显然的,我们肯定是要查出每一个点到达根节点的距离来预先得到一个塞点顺序,这样就保证了每一个点塞入的时候其祖先节点都已经在了。
然后我们考虑询问。可以得到,我们每一次询问实际上是可以得到这个点与另一个点 ( ext{lca}) 的深度的,在我们已经知道了每一个点深度的情况下。
这样我们就可以这么设计询问:每一次在移动完轻边后查询当前节点重链低端的点和塞入点的 ( ext{lca}) 的深度,如此我们就可以轻易得到下一次跳轻边的位置(因为是二叉树),然后重复上述步骤就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e3+5;
int n,fa[N];
struct Node{int id,dep,size,son[2],bot;}tr[N];//0 is heavy
bool cmp(const Node a,const Node b){return a.dep<b.dep;}
void work(int rt,int u,int goal){
tr[rt].size++;
// printf("%d %d %d %d
",tr[rt].id,tr[u].id,now,goal);
// printf("%d %d %d
",tr[rt].son[0],tr[rt].son[1],tr[rt].bot);
if(tr[rt].dep>goal){
if(!tr[rt].son[0]){
tr[rt].son[0]=tr[rt].bot=u;
fa[tr[u].id]=tr[rt].id;
return ;
}
int dis;
printf("? %d %d
",tr[tr[rt].bot].id,tr[u].id);
fflush(stdout),scanf("%d",&dis);
goal=(tr[tr[rt].bot].dep+tr[u].dep-dis)/2;
}
if(tr[rt].dep==goal){
if(!tr[rt].son[0]){
tr[rt].son[0]=tr[rt].bot=u;
fa[tr[u].id]=tr[rt].id;
return ;
}
if(!tr[rt].son[1]){
tr[rt].son[1]=u;
fa[tr[u].id]=tr[rt].id;
return ;
}
work(tr[rt].son[1],u,goal);
if(tr[tr[rt].son[1]].size>tr[tr[rt].son[0]].size)
swap(tr[rt].son[0],tr[rt].son[1]);
tr[rt].bot=tr[tr[rt].son[0]].bot;
return ;
}
work(tr[rt].son[0],u,goal);
tr[rt].bot=tr[tr[rt].son[0]].bot;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) tr[i].id=i;
for(int i=2;i<=n;++i){
printf("? %d %d
",1,i);
fflush(stdout),scanf("%d",&tr[i].dep);
}
sort(tr+2,tr+1+n,cmp);
for(int i=1;i<=n;++i) tr[i].size=1,tr[i].bot=i;
for(int i=2;i<=n;++i) work(1,i,-1);
printf("!");
for(int i=2;i<=n;++i) printf(" %d",fa[i]);
return printf("
"),0;
}