Samara Farewell Contest 2020 (XXI Open Cup, GP of Samara) M. Binary Search Tree(思维题-二叉搜索树的合法节点)

题目

给一棵n(n<=5e5)个点的树,问是否存在节点x,使得以x为根时,树恰为二叉搜索树

如果不存在输出-1,否则增序输出所有可能的节点x

思路来源

乱搞AC

题解

大概能树形dp搞吧,但是不会

于是变成了一道一堆特判冲过去的题

首先特判一些情况,

①存在度数>=4的点的时候无解,②n<=2必有解(此时根的度为1)

注意到,③答案可能的节点,一定是最小值和最大值之间的这条链上的节点,

不然必有一个值的大小关系和最值是矛盾的,

所以枚举这条链上的点u,u最多相邻三个点,链上左点lu,链上右点ru,和子节点v,

④如果有子节点v,则v必须是一棵二叉搜索子树:

即如果v<u,则mx[v]必须小于u,反之同理;

记v<u的子树为左子树,v>u的子树为右子树,则u最多只有一棵左子树和一棵右子树(废话)

⑤此外还有一个重要特判,最小值和最大值最多只能连接两个点,

即最小值只能有右子树和父亲两个比它大的值,

考虑n=4,m=3,1 2, 1 3, 1 4三条边的一棵树,是无解的

⑥这条链上的点,从最小值到最大值之间的值,如果不单调递增,也是无解的

⑦对于u来说,如果有v<u的子树,即左子树v,则链上左点lu在这棵树中一定比链上右点高,

说明根节点只能是lu及lu以左的点,反之同理,每一棵v子树都对根节点有一个区间限制

若最终区间为空,无解;否则,区间内每一个度不超过2的点都是一个合法的解

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=5e5+10;
vector<int>e[N],f,ans;
int n,u,v,par[N],mn[N],mx[N],Mn,Mx,c;
bool sol=1,link[N],no[N];
void dfs(int u,int fa){
	par[u]=fa;
	int small=0,big=0;
	for(auto &v:e[u]){
		if(v==fa)continue;
		dfs(v,u);
		if(v<u){
			if(mx[v]>u)sol=0;
			small++;
		}
		else{
			if(mn[v]<u)sol=0;
			big++;
		} 
		mn[u]=min(mn[u],mn[v]);
		mx[u]=max(mx[u],mx[v]);
	}
	if(small>=2 || big>=2){
		no[u]=1;
	}
	if((u==1 || u==n) && e[u].size()==3){
		sol=0;
	}
} 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		mn[i]=mx[i]=i;
	}
	for(int i=1;i<n;++i){
		scanf("%d%d",&u,&v);
		if(u==v){
			puts("-1");
			return 0;
		}
		e[u].pb(v);e[v].pb(u);
		if(e[u].size()>=4 || e[v].size()>=4){//度=4 
			puts("-1");
			return 0;
		}
	}
	if(n<=2){//n<=2
		for(int i=1;i<=n;++i){
			printf("%d%c",i," \n"[i==n]);
		}
		return 0;
	}
	dfs(n,n+1); 
	if(!sol){//非二叉搜索子树  
		puts("-1");
		return 0;
	}
	for(int u=1;;u=par[u]){//不单调 
		if(u>par[u]){
			puts("-1");
			return 0;
		}
		link[u]=1;
		f.push_back(u);
		if(u==n)break;
	} 
	for(auto &u:f){
		for(auto &v:e[u]){
			if(link[v])continue;
			if(no[v]){
				puts("-1");
				return 0;
			}
		}
	}
	Mx=0;Mn=f.size()-1;
	for(int i=0;i<f.size();++i){
		int u=f[i];
		for(auto &v:e[u]){
			if(link[v])continue;
			if(v<u){
				if(mn[v]<f[i-1]){
					puts("-1");
					return 0;
				}
				Mn=min(Mn,i-1);
			}
			else if(v>u){
				if(mx[v]>f[i+1]){
					puts("-1");
					return 0;
				}
				Mx=max(Mx,i+1);
			}
		}
	}
	for(int i=Mx;i<=Mn;++i){
		int u=f[i];
		if(e[u].size()>2)continue;
		ans.pb(u);
	}
	sort(ans.begin(),ans.end());
	for(int i=0;i<ans.size();++i){
		printf("%d%c",ans[i]," \n"[i==(int)ans.size()-1]);
	}
	return 0;
} 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值