[ACNOI2022]树上构造

98 篇文章 0 订阅
14 篇文章 0 订阅

题目

题目背景
我的世界里,闯入两尊神:其一 炎翼鸟,其二 英陀罗

夜卧床,窗外无光,脑中却是一片光明——是 炎翼鸟 的翅膀上的火焰,这火焰点燃的太阳。光芒如此灼烈,用手遮住双眼也徒劳:光弥散在空气中,滴落在衣服上。

夜无眠。日初升。我已伸手不见五指——是 英陀罗完全体丶须佐科夫,遮天蔽日。见过它的人都离死神不远。我知道光在外面,可光透不过这套子。

我,一介凡人,何以逆天?我就在颠倒的日夜中,努力地求着生存!

题目描述
对于一棵树,给出 p i p_i pi 表示树上距离 i i i 最远的点。如果这样的点有多个,则 p i = − 1 p_i=-1 pi=1

请根据 { p i } \{p_i\} {pi} 还原出任意一棵可能的树。若无解输出 Impossible 即可。

数据范围与约定
点数 n ⩽ 1 0 6 n\leqslant 10^6 n106

思路

本题虽说是构造题,尚且不算天马行空,可做。然吾费时于 T 2 T2 T2 却无果,悲夫!

最远点,其实是大家都很熟悉的东西。然而我似乎没能很快地联想到这些东西……

经典结论:任意点的最远点集与任意直径的端点集合有交,且任意点的最远点集只含直径端点。

由前半句,若 p i ≠ − 1 p_i\ne -1 pi=1,对于任意直径,其端点之一必然是 p i p_i pi,即 p i p_i pi 在所有直径端点的交集之中。

因为一些历史原因(原题面对部分分的描述方式)设 S = { p i ∣ i ∈ [ 1 , n ] } ∪ { − 1 } S=\{p_i\mid i\in[1,n]\}\cup\{-1\} S={pii[1,n]}{1} 。由于直径是很重要的信息,而 S S S 与直径直接挂钩,故下面做了些分类讨论。

∣ S ∣ = 3 |S|=3 S=3

p i p_i pi 在所有直径端点的交集之中,所以 ⟨ a , b ⟩ \langle a,b\rangle a,b 必然是唯一直径。若 p a ≠ b p_a\ne b pa=b 则存在另一条直径 ⟨ a , c ⟩ \langle a,c\rangle a,c,矛盾,故 p a = b p_a=b pa=b 。同理有 p b = a p_b=a pb=a

放好 p i = − 1 p_i=-1 pi=1 的点很简单,就是在直径 a , b a,b a,b 的中点上挂儿子,包括中点本身也是 p i = − 1 p_i=-1 pi=1 的点。放 p i = b p_i=b pi=b 则需要在中点靠近 a a a 的部分挂儿子,然后 a a a 到中点的链上都是 p i = b p_i=b pi=b 的点。对于 p i = a p_i=a pi=a 则对称操作。

仔细思考发现,我们可以缩减到只需要两个 p i = a    ( i ≠ b ) p_i=a\;(i\ne b) pi=a(i=b) p i = b    ( i ≠ a ) p_i=b\;(i\ne a) pi=b(i=a) 的点,即一个 7 7 7 个点(或 6 6 6 个点)的直径。可以画图证明这是必要的。注意,若二者的数量均为 1 1 1,那么 5 5 5 个点(或 4 4 4 个点)的直径就可以实现了,这是仅有的特例。

∣ S ∣ = 1 |S|=1 S=1

p i = − 1 p_i=-1 pi=1 恒成立。不难想到构造菊花图。

∣ S ∣ = 2 |S|=2 S=2

知道直径的一端是 a a a,另一端至少有 2 2 2 个选择 b , c b,c b,c

p b ≠ a p_b\ne a pb=a 则存在直径 ⟨ b , d ⟩    ( d ≠ a ) \langle b,d\rangle\;(d\ne a) b,d(d=a),与 p i = a p_i=a pi=a 为任意直径的端点之一矛盾。同理,必有 p b = p c = a p_b=p_c=a pb=pc=a

从小处着笔。比如就让 dis ( b , c ) = 2 \text{dis}(b,c)=2 dis(b,c)=2,设二者之间的点是 ν \nu ν,设直径长度为 5 5 5 个点。画图可见, ν \nu ν 与其子树(不含 a a a 的部分)都是 p = a p=a p=a 的点,除此之外都是 p = − 1 p=-1 p=1 的点。

但我们需要三个 p = a p=a p=a 的点,因为有 p ν = a p_{\nu}=a pν=a 存在。试试证明其必要性:以 a a a 为根,记 κ \kappa κ b , c b,c b,c 的最近公共祖先,必然有 dis ( κ , a ) > dis ( κ , b ) = dis ( κ , c ) \text{dis}(\kappa,a)>\text{dis}(\kappa,b)=\text{dis}(\kappa,c) dis(κ,a)>dis(κ,b)=dis(κ,c),否则 ⟨ b , c ⟩ \langle b,c\rangle b,c 将成为直径。若 p κ ≠ a p_\kappa\ne a pκ=a,则 κ \kappa κ 存在一个最远点 w    ( w ∉ { a , b , c } ) w\;(w\notin\{a,b,c\}) w(w/{a,b,c}),因最远点总是直径端点,且 p i = a p_i=a pi=a 是所有直径的公共端点,所以 ⟨ w , a ⟩ \langle w,a\rangle w,a 是一条直径,类似于 b , c b,c b,c p w = a p_w=a pw=a

所以,要么 p w = a p_w=a pw=a,要么 p κ = a p_{\kappa}=a pκ=a,总是能找到三个 p i = a p_i=a pi=a 的点。

当然,我们还需要一个 p = − 1 p=-1 p=1 的点(除 p a = − 1 p_a=-1 pa=1 以外),但其必要性显然:考虑 a a a κ \kappa κ 方向走一步的点,不可能有 p i = a p_i=a pi=a

综述

基本上都是从小处着眼,设计一个长度较小的直径即可。算是良心构造题了,只是麻烦而已

代码

#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MAXN = 1000006;
int p[MAXN], a, b, fa[MAXN];

int main(){
	int n = readint();
	rep(i,1,n) if((p[i] = readint()) != -1){
		if(a == p[i] || b == p[i]) continue;
		if(b){ puts("Impossible"); return 0; }
		if(a) b = p[i]; else a = p[i];
	}
	if(n <= 3){ // must be a chain
		if(n == 1){
			puts(p[1] == 1 ? "Possible" : "Impossible");
			return 0;
		}
		if(a && b && p[a] == b && p[b] == a && (n != 3 || p[a^b] == -1)){
			puts("Possible");
			if(n == 3) printf("%d %d\n%d %d\n",a,a^b,b,a^b);
			else puts("1 2"); // n == 2
		}
		else puts("Impossible");
		return 0; // case closed
	}
	if(!a){ // p_i = -1
		puts("Possible");
		rep(i,2,n) printf("1 %d\n",i);
	}
	else if(!b){ // |S| = 2
		if(n == 4 || p[a] != -1){
			puts("Impossible"); return 0;
		}
		int top = a, cnt = 0, len = 0;
		rep(i,1,n) if(p[i] == a) b = i, ++ cnt;
		if(cnt < 3 || cnt == n-1){
			puts("Impossible"); return 0;
		}
		rep(i,1,n) if(i != a && i != b){
			if(p[i] == a) fa[i] = b;
			else if(len == 2) fa[i] = top;
			else fa[top] = i, ++ len, top = i;
		}
		fa[top] = b; // link
		puts("Possible");
		rep(i,1,n) if(i != b)
			printf("%d %d\n",i,fa[i]);
	}
	else{ // |S| = 3
		if(p[a] != b || p[b] != a){
			puts("Impossible"); return 0;
		}
		int top[2] = {a,b}, len[2] = {0,0};
		rep(i,1,n) if(i != a && i != b && p[i] != -1){
			const int bel = (p[i] == a);
			if(len[bel] == 2) fa[i] = top[bel];
			else fa[top[bel]] = i, ++ len[bel], top[bel] = i;
		}
		int lst = 0; // chain of (p_i = -1)
		rep(i,1,n) if(!(~p[i])) // get anus!
			(lst) ? (fa[i] = lst) : (lst = i);
		if(len[0] && len[0] == len[1]){
			if(lst) fa[top[0]] = lst, fa[lst] = top[1];
			else fa[top[0]] = top[1]; // link
			puts("Possible");
			rep(i,1,n) if(i != top[1])
				printf("%d %d\n",i,fa[i]);
		}
		else puts("Impossible");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值