POJ - 2201 Cartesian Tree(笛卡尔树-单调栈/暴跳父亲)

穿精又带淫゛_ 提交于 2019-11-25 22:19:49

题目链接:点击查看

题目大意:给出n个节点的key和val,构造出其笛卡尔树的原型

笛卡尔树的定义:

所谓笛卡尔树,就是将给定的n个二元组(key,val)建成一棵树。使得:

  1. 如果只关注key,那么这是一棵二叉搜索树
  2. 如果只关注val,那么这是一个堆
  3. 对于任意三个节点fa,ls,rs,满足:
    1. key[ls]<key[fa]<key[rs]
    2. val[fa]>=max(val[ls],val[rs])

题目分析:首先我们需要先知道怎么建立笛卡尔树,我们可以按照key进行排序,经过排序之后,在最右边的树链上操作,对于每一个节点的val寻找一下该节点需要插入的位置,需要满足的条件是当前节点插入后的val值满足条件比其上面的节点要大,比起下面的节点要小,找到合适的位置后,将原本的链断开,将x插入,并将原本x之下的那条链连接到x的左子树上,这样我们就成功插入一个节点x了

先说一下为什么要这样插点,因为我们需要满足二叉搜索树的条件并且已经提前按照key值非降排好序了,所以在插入节点x之前,树中的所有key值都比节点x的key值要小,若我们只关注key的话,那么直接把点x插入到主根root节点向右遍历最下面的那个节点的右儿子即可,也就是最右端树链的末端,因为同时需要满足对于val值建立小顶堆的条件,所以我们需要在最右端的树链上寻找一个合适的位置插入节点x,当找到合适的位置后,这个位置肯定是满足了对于val值建堆的条件了,但却不满足对于key建二叉搜索树的条件了,所以我们需要稍微操作一下,因为当前x的key值在整棵树中是最大的,所以对于其父节点以及其祖先节点,肯定在右链的末端是合适的位置,但对于节点x下面的树链,也就是原本这个位置右链的下半部分来说,我们需要将这下半部分转移到当前节点x的左链上,这样就可以同时满足上述两个条件了

可能看起来比较难理解,可以自己动手画一下上面的这个过程,我也是稍微画了画图之后就豁然开朗了

现在说一下实现方法,对于key排序没什么好说的,现在就是对于每个点x需要找到点x插入的位置,这个位置肯定是从右链最下面开始,找到小于等于点x的第一个节点作为点x的父节点,我们该怎么快速找到这个父节点呢

网上的正解都是清一色的用单调栈来维护,确实,因为我们只需要在右链上寻找合适的位置,而且整棵树对于val满足堆的性质,所以从根部沿着右链到达叶子结点,这一整条树链实际上就是一个非严格递减的序列,这样一来我们就可以直接用单调栈来维护了,每次维护完之后栈顶就是我们需要寻找的节点编号了,注意,单调栈里维护的为每个节点的下标,也就是id,通过这个id就可以直接访问其内部成员了,用单调栈的话因为每个成员至多访问一次,所以时间复杂度也就是O(n)建树了

不过zx学长想出了另一个方法,也就是根据建树的整个过程得到启发的,因为每次我们找到一个放置点x的位置后,整个树链的下半部分都会拐个弯,拐到节点x的左节点去了,所以此时最右边的树链相当于将原本下半部分的节点删除掉了,然后此时的点x一定是最右端树链的最末端的节点,此时我们每次可以沿着最末端的节点往上跳,跳完之后直接将下半部分的树链拐弯,这样理论上每个节点至多也是遍历一次,时间复杂度是O(n)

其实两种方法大同小异,都离不开笛卡尔树的两个关键性质,理解了如何根据其性质建树之后就想怎么建就怎么建了。。

代码:

单调栈:

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
    
typedef long long LL;
    
const int inf=0x3f3f3f3f;
    
const int N=5e4+100;

struct Node
{
	int l,r,fa,val,key,id;
	bool operator<(const Node& a)const
	{
		return key<a.key;
	}
}tree[N],ans[N];

stack<int>s;//维护id,维护val的小顶栈 

void insert(int x)
{
	while(s.size()&&tree[s.top()].val>tree[x].val)//单调栈维护非严格递增序列
		s.pop();
	tree[x].l=tree[s.top()].r;//更新 节点x 的左儿子
	tree[tree[s.top()].r].fa=x;//更新 节点x 的左儿子 的父节点
	tree[x].fa=s.top();//更新 节点x 的父节点
	tree[s.top()].r=x;//更新 节点x 的父节点 的右儿子
	s.push(x);
}

void init()
{
	tree[0].val=-inf;
	tree[0].l=tree[0].r=0;
	s.push(0);
}
  
int main()
{
//  freopen("input.txt","r",stdin);
//  ios::sync_with_stdio(false);
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		init();
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&tree[i].key,&tree[i].val);
			tree[i].id=i;
		}
		sort(tree+1,tree+1+n);
		for(int i=1;i<=n;i++)
			insert(i);
		for(int i=1;i<=n;i++)
		{
			ans[tree[i].id].fa=tree[tree[i].fa].id;
			ans[tree[i].id].l=tree[tree[i].l].id;
			ans[tree[i].id].r=tree[tree[i].r].id;
		}
		printf("YES\n");
		for(int i=1;i<=n;i++)
			printf("%d %d %d\n",ans[i].fa,ans[i].l,ans[i].r);
	}
      
       
       
       
        
        
        
        
         
        
    return 0;
}

暴跳父亲:

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
    
typedef long long LL;
    
const int inf=0x3f3f3f3f;
    
const int N=5e4+100;

struct Node
{
	int l,r,fa,val,key,id;
	bool operator<(const Node& a)const
	{
		return key<a.key;
	}
}tree[N],ans[N];

void insert(int pos)
{
	int cur=pos-1;
	while(tree[cur].val>tree[pos].val)//用while暴跳父亲,直到父亲节点的val小于等于当前节点的val
		cur=tree[cur].fa;
	tree[tree[cur].r].fa=pos;//更新 节点x 的左儿子 的父节点
	tree[pos].l=tree[cur].r;//更新 节点x 的左儿子
	tree[cur].r=pos;//更新 节点x 的父节点 的右儿子
	tree[pos].fa=cur;//更新节点x 的父节点
}

void dfs(int x)
{
	if(!x)
		return;
	ans[tree[x].id].fa=tree[tree[x].fa].id;
	ans[tree[x].id].l=tree[tree[x].l].id;
	ans[tree[x].id].r=tree[tree[x].r].id;
	dfs(tree[x].l);
	dfs(tree[x].r);
}

void init()
{
	tree[0].val=-inf;
	tree[0].l=tree[0].r=0;
}
  
int main()
{
//  freopen("input.txt","r",stdin);
//  ios::sync_with_stdio(false);
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		init();
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&tree[i].key,&tree[i].val);
			tree[i].id=i;
		}
		sort(tree+1,tree+1+n);
		for(int i=1;i<=n;i++)
			insert(i);
		dfs(tree[0].r);
		printf("YES\n");
		for(int i=1;i<=n;i++)
			printf("%d %d %d\n",ans[i].fa,ans[i].l,ans[i].r);
	}
      
       
       
       
        
        
        
        
         
        
    return 0;
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!