考虑直接在原树上从下往上分析,刻画 $F$ 构成子段的性质。可以认为每个子树上传一个 $[1,size_u]$ 的排列表示内部的顺序,通过分析 $G_{n-1}$ 发现每个子树的 $p$ 一定都是一段区间。
对 $u$ 合法的必要条件就是按照给出的顺序合并 $u$ 的出边,每次的 $F(u)$ 构成区间。最终放在 $p_u$ 左边的子树只需要对上传的序列进行 reverse 然后拼接起来。对于 $u\ne rt$ 考虑,插入边 $(u,fa_u)$ 时此刻 $u$ 内部的顺序构成 $[1,size_u]$ 的一段前缀,否则 $F(fa_u)$ 此刻无法构成区间。
最终 $u$ 出边的结构一定是:$x_1,x_2,\cdots,x_l,u,y_1,y_2,\cdots,y_r$,其中 $x,y$ 构成子节点的一组划分,各自递归到子问题中。然后每一侧一定是放完靠近 $u$ 的子树后才能接着开始放下一个子树。设 $a_u$ 表示 $(u,fa_u)$ 的加入时刻,$b_u=\max_{v\in subtree(u)}a_v$,对 $x$ 有 $a_{x_i}>b_{x_i+1}$,$y$ 有 $b_{y_i}\leq a_{y_{i+1}}$。然后考虑 $a_u$ 即 $(u,fa_u)$ 的限制,如果在 $x$ 一侧就一定需要 $x$ 都填写完毕,此时有 $a_u>b_{x_1}$。
此时这个结构比较好,考虑直接按照子节点的 $a_v$ 从大到小排序,从两边向中间填写,记录两侧各自对 $b$ 的限制,每次往较小的填上当前子树一定不劣,如果填不进去就无解。而 $a_u$ 会作为其中一类的初始限制。
由于输入已经排序好,时间复杂度 $O(n)$。
感觉说的不太是人话,建议看代码
void solve(){
cin>>n;
F(_,1,n-1){ int u,v;cin>>u>>v,g[u].push_back(v),a[v]=_; }
F(i,1,n)if(!a[i])rt=i;
auto dfs0=[&](auto&self,int u)->void{
b[u]=a[u];
for(int&v:g[u])self(self,v),chkmax(b[u],b[v]);
}; dfs0(dfs0,rt);
a[rt]=n;
bool flag=0;
auto dfs=[&](auto&self,int u,int op)->void{
if(flag)return;
reverse(all(g[u]));
array<int,2>f={inf,inf};
vector<int>vec[2];
F(o,0,1)vec[o].clear();
f[op^1]=a[u];
for(int&v:g[u]){
int c;
if(b[v]<min(f[0],f[1]))c=f[0]<f[1]?0:1;
else if(b[v]<f[0])c=0;
else if(b[v]<f[1])c=1;
else return flag=1,void();
vec[c].push_back(v),f[c]=a[v];
}
for(int&v:vec[0])self(self,v,0);
ans[u]=++tim;
reverse(all(vec[1]));
for(int&v:vec[1])self(self,v,1);
}; dfs(dfs,rt,0);
if(flag)return cout<<"No\n",void();
cout<<"Yes\n";
F(i,1,n)cout<<ans[i]<<' ';
cout<<'\n';
}