[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);
}
posted @ 2021-09-23 20:14  C202044zxy  阅读(309)  评论(0编辑  收藏  举报