把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AT4114】[ARC095D] Permutation Tree(简单题)

点此看题面

  • 对于一个长度为\(n\)的排列\(p\)中的每一个\(i\),找到最大的\(j\)满足\(p_j<p_i\),在\(i,j\)间连边,由此得到出一棵树。
  • 给定一棵\(n\)个点的树,要求构造一个字典序最小的排列,使得生成的树与给定树同构,或判无解。
  • \(n\le10^5\)

生成树的本质

考虑它的生成方式,我们按\(p_i\)从小到大插入序列。

那么每次最大的小于当前\(p_i\)\(p_j\),就是序列中的最后一个数。

而当前数可以插入到序列中的任意位置,也就是说既可以放在最后面成为新的最后一个数,也可以放在前面。

总结一下,抛开这个所谓的排列,就相当于每次我们有两个选择:依旧与原本的点连边,或是与上一个点连边。

那么这样建出来的一棵树,就是一条链,每个点上挂了若干个叶节点。

最优的构造方案

首先如果给定树不能表示成这种形式,即非叶节点并不在一条链上,显然无解。

否则,一条链无非两种方向,我们大可分别求出两种方案下的答案,取个较优解即可。这样一来我们就能确定出根节点,那么父子关系也就确立了。

考虑一个非叶节点,它必然需要在它的祖先及祖先的其与子节点都填完之后才能填。

它的子节点们权值必须要全比它大,且除了非叶节点儿子要填在它之后外,所有的叶节点儿子都必须填在它之前(否则后续点就会向在它之后的儿子连边)。

因此假设当前点能取的权值为\(nw\),有\(s_x\)个叶节点儿子,我们的填法必然是先填上\(nw+1\sim nw+s_x\)(叶节点儿子们),再填上\(nw\)(当前点)。

最后还要注意一下,把链首非叶节点和链尾非叶节点的一个叶节点儿子也算到链上(也就是把它们当成非叶节点来对待),肯定能得到更优的答案。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,d[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc(' ');}
}using namespace FastIO;
int c,s[N+5];I void dfs(CI x,CI lst=0)//遍历一遍,依次记录每个非叶节点的叶节点儿子数
{
	s[++c]=d[x]-2;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&d[e[i].to]^1&&(dfs(e[i].to,x),0);
}
int ans1[N+5],ans2[N+5];I void Get(int* ans)//求出序列
{
	for(RI i=0,j=1,k;i<=c+1;++i) {for(k=1;k<=s[i];++k) ans[j+k-1]=j+k;ans[j+s[i]]=j,j+=s[i]+1;}//先填叶节点儿子,再填当前点
}
I void Cmp(int* ans1,int* ans2)//两种方案比较,取字典序较小的
{
	for(RI i=1;i<=n;++i) if(ans1[i]^ans2[i]) {if(ans1[i]<ans2[i]) return;break;}//最早的不同位,若ans1更小直接return
	for(RI i=1;i<=n;++i) ans1[i]=ans2[i];//ans2更小,令ans1=ans2
}
int main()
{
	RI i,j,x,y;for(read(n),i=1;i^n;++i) read(x,y),add(x,y),add(y,x),++d[x],++d[y];
	if(n==2) return puts("1 2"),0;//特判n=2
	RI rt,t;for(i=1;i<=n;++i) if(d[i]^1)
	{
		for(t=0,j=lnk[i];j;j=e[j].nxt) d[e[j].to]^1&&++t;if(t>2) return puts("-1"),0;//如果非叶节点不是链,无解
		if(t<=1) rt=i;//以度数=1的点为根(之所以写≤1是因为菊花图可能只有一个非叶节点)
	}
	dfs(rt),Get(ans1),reverse(s,s+c+2),Get(ans2);//先dfs一遍求出链上每个点的叶节点儿子数,然后按两种方向分别求解
	for(Cmp(ans1,ans2),i=1;i<=n;++i) write(ans1[i]);return clear(),0;//输出字典序较小的答案
}
posted @ 2021-05-09 16:26  TheLostWeak  阅读(71)  评论(0编辑  收藏  举报