[LOJ 6669] Nauuo and Binary Tree
一、题目
二、解法
注意到 \(n\leq 3000\),那么对于 \(30000\) 次询问的理解方式就是用 \(\log\) 次确定一个点的父亲。
不难想到把所有点按深度分层,对于所有点求出和 \(1\) 的距离即可。
我们考虑每做一次询问就要把父亲的范围缩小一半,因为二叉树本身就带有这种分治的功能,我们考虑从 \(1\) 开始确定,每次走左右儿子。但是因为题目给的二叉树可能形态迥异,所以暴力走是不行的。
这变成一个优化结构问题了,我们要保证原树的形态所以点分治之类的技巧是行不通的,但树链剖分是一个很好的结构,我们可以只走轻边来保证复杂度。
如何实现只走轻边?我们把重链底段的节点和待确定节点问一下距离,如果为 \(1\) 那么找到了父亲;否则可以反解出它们的 \(\tt lca\) 深度,进而找到它们的 \(\tt lca\),那么答案一定在重链的反方向,我们沿着这条轻边往下走即可。
因为每次只用对深度前 \(i\) 的节点树剖,所以 \(30000\) 的询问次数绰绰有余。
三、总结
对结构的理解很重要,二叉树本身就是一种分治结构,优化结构的方法也很重要。
//higher and higher , chase it
#include <cstdio>
#include <vector>
using namespace std;
const int M = 3005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,tot,f[M],d[M],ch[M][2],fa[M],dw[M],siz[M];
vector<int> v[M];
int ask(int u,int v)
{
printf("? %d %d\n",u,v);
fflush(stdout);
return read();
}
void dfs(int u)
{
if(!u) return ;
siz[u]=1;
dfs(ch[u][0]);dfs(ch[u][1]);
siz[u]+=siz[ch[u][0]]+siz[ch[u][1]];
if(siz[ch[u][0]]>siz[ch[u][1]])
dw[u]=dw[ch[u][0]];
else dw[u]=dw[ch[u][1]];
if(siz[u]==1) dw[u]=u;
}
signed main()
{
n=read();
for(int i=2;i<=n;i++)
{
d[i]=ask(1,i);
v[d[i]].push_back(i);
}
for(int i=1;i<=n;i++)
{
dfs(1);
for(auto u:v[i])
{
int x=1;
while(d[x]!=i-1)
{
int y=dw[x],t=(d[y]+d[u]-ask(y,u))/2;
t=d[y]-t;while(t--) y=fa[y];
if(d[y]==i-1) {x=y;break;}
if(siz[ch[y][0]]>siz[ch[y][1]])
x=ch[y][1];
else x=ch[y][0];
}
fa[u]=x;ch[x][ch[x][0]?1:0]=u;
}
}
printf("!");
for(int i=2;i<=n;i++)
printf(" %d",fa[i]);
puts("");
fflush(stdout);
}