CF772E Verifying Kingdom

在我的洛谷博客中查看

题解

因为原树的所有非叶子节点都恰好有两个儿子,所以原树上 (n) 个叶子节点形成的虚树与原树同构。

考虑增量地构造虚树:假如前 ((i-1)) 个叶子的虚树已经求出来了,那么怎么找到第 (i) 个叶子的位置呢?

首先我们可以通过一次询问,确定叶子节点 (i) 相对于某个非叶子节点 (u) 的位置。具体地,设 (x,y) 分别为 (u) 的左、右子树内的两个叶子节点,那么可以通过询问 ((x,y,i)) 来确定 (i)(u) 的左子树或右子树或与 (u) 没有祖先关系。

观察数据范围:询问次数不超过 (10n)(10=lceil log_2 1000 ceil)

于是想到点分治:对于每个叶子 (i),都进行一次点分治。每次点分治对于当前连通块找到重心 (rt)(注意这个重心不能是叶子节点,若分治到一个只有单一叶子节点的连通块,新建点作为这个连通块和 (i) 的父亲即可),并询问 (rt)(i) 的位置关系。设与 (rt) 相邻的、朝向 (i) 的那个点(这个点有可能是 (rt) 的儿子,也有可能是 (rt) 的父亲)为 (v),基于这个位置关系,有如下几种可能:

  • (v) 之前没有作为分治中心过:递归到 (v) 进行点分治即可。

  • (i) 不在 (rt) 的子树内(即需要往上走):

    • (rt) 是虚树的根:我们需要在 (rt) 上面新加一个点,作为新的根,并令 (rt,i) 为新点的左右儿子。
    • 否则,此时我们已经知道了 (v) 曾经作为分治中心,并且它一定是上一轮点分治的分治中心。也就是说,(i,rt)(v) 的同一子树内,且 (i) 不在 (rt) 的子树内。这显然是不可能的,因此我们要新建一个点,其左右儿子分别为 (rt,i) 而父亲为 (v)
  • (i)(rt) 的子树内,而 (v) 之前被访问过:同上新建点即可。

另外,注意到前两个叶子节点是不需要点分治确定位置的,因为它们在虚树上一定有共同的父亲。

因为每次点分治都只会分治到某个子树,而新一轮分治的连通块大小会减半,因此时间复杂度为 (mathcal{O}(n(n+frac{n}{2}+frac{n}{4}+dots))=mathcal{O}(n^2)),询问次数为 (mathcal{O}(nlog n))

代码
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <cassert>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
template<typename T> void Read(T &x){
	x=0;int _f=1;
	char ch=getchar();
	while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();
	while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
	x=x*_f;
}
template<typename T,typename... Args> void Read(T &x,Args& ...others){
	Read(x);Read(others...);
}
typedef long long ll;
const int N=2005;
int n,tot,e[N][3],sym[N][2],root;
//e[u][0] 表示 u 的父亲,e[u][1,2] 分别表示 u 的左右儿子
//sym[u][0,1] 分别表示以 u 的左、右儿子为根的子树内的任一叶子节点
void Link(int u,int x,int v){
	e[u][x]=v,e[v][0]=u;
}
int vis[N],siz[N],mxsiz[N];
int GetSize(int u,int pre){
	int res=1;
	For(i,0,2){
		if(!e[u][i]||e[u][i]==pre||vis[e[u][i]]) continue;
		res+=GetSize(e[u][i],u);
	}
	return res;
}
int rt;
void GetRoot(int u,int pre,int tsiz){
	siz[u]=1,mxsiz[u]=0;
	For(i,0,2){
		if(!e[u][i]||e[u][i]==pre||vis[e[u][i]]) continue;
		GetRoot(e[u][i],u,tsiz);
		mxsiz[u]=max(mxsiz[u],siz[e[u][i]]);
		siz[u]+=siz[e[u][i]];
	}
	mxsiz[u]=max(mxsiz[u],tsiz-siz[u]);
	if(e[u][1]&&mxsiz[u]<=tsiz/2) rt=u;
}
void Decomp(int u,int lf){//点分治。lf:需要确定位置的叶子节点
	if(!e[u][1]){//特判分治到叶子的情况
		int f=e[u][0],sn;
		if(e[f][1]==u) sn=1;
		else sn=2;
		Link(f,sn,++tot);Link(tot,1,u);Link(tot,2,lf);
		sym[tot][0]=u,sym[tot][1]=lf;
		return;
	}
	rt=0;
	int s=GetSize(u,0);
	GetRoot(u,0,s);
	u=rt;
	vis[u]=1;
	printf("%d %d %d
",sym[u][0],sym[u][1],lf);
	fflush(stdout);
	int nxt;char temp[10];
	scanf("%s",temp);
	if(temp[0]=='X') nxt=0;
	else if(temp[0]=='Y') nxt=2;
	else nxt=1;
	if(e[u][nxt]&&!vis[e[u][nxt]]){
		Decomp(e[u][nxt],lf);
	}else{
		int v=e[u][nxt];
		if(nxt==0){//往上走
			if(!v){
				Link(++tot,1,u);Link(tot,2,lf);
				sym[tot][0]=sym[u][0],sym[tot][1]=lf;
				root=tot;return;
			}
			int sn=e[v][1]==u?1:2;
			Link(v,sn,++tot);Link(tot,1,u);Link(tot,2,lf);
			sym[tot][0]=sym[u][0],sym[tot][1]=lf;
		}else{//往左/右儿子走
			Link(u,nxt,++tot);Link(tot,1,v);Link(tot,2,lf);
			if(!e[v][1]) sym[tot][0]=v;
			else sym[tot][0]=sym[v][0];
			sym[tot][1]=lf;
		}
	}
}
int main(){
	scanf("%d",&n);
	root=tot=n+1;
	Link(root,1,1);Link(root,2,2);
	sym[n+1][0]=1,sym[n+1][1]=2;
	For(i,3,n){
		memset(vis,0,sizeof vis);
		Decomp(root,i);
	}
	puts("-1");
	For(i,1,tot) printf("%d ",e[i][0]?e[i][0]:-1);
	return 0;
}
Written by Alan_Zhao
原文地址:https://www.cnblogs.com/alan-zhao-2007/p/cf772e-sol.html