笛卡尔树 poj2201

20 篇文章 0 订阅
4 篇文章 0 订阅

笛卡尔树

在左学长的督促下,我开始了学习笛卡尔树的旅途(挖坑)。。。

笛卡尔树是一种非常神奇的树,他的每一个节点都有两个关键字key和val,首先其基本结构是一颗二叉搜索树,即对于每一个节点,其左子树的key值都应该比其小,右子树则大,满足这些条件的同时,他的val值在这颗树上又有大小根堆的性质(优先队列),这就是笛卡尔树的基本性质。

那么,笛卡尔树能干些啥呢?

1、笛卡尔树可以有效地处理范围最值查询,通过将定义在数列上的RMQ问题转化为定义在树结构上的最低公共祖先问题。数列以线性时间构造出笛卡尔树,笛卡尔树则能以常数时间处理最低公共祖先查询,因此在线性时间的预处理后,范围最值查询能以常数时间完成。
2、Bender&Farach-Colton (2000)[2]则提出了RMQ与LCA问题的新联系,他们通过不基于树的算法处理RMQ问题从而有效地解决LCA问题。其使用欧拉路径的技巧将树结构转化为数列,此数列具有特定性质(相邻数值代表树中的相邻顶点,即在树中高度差为1的顶点),利用这一性质RMQ问题可以很高效地得到解决。通常的数列则不具备此性质,为了将一般的数列转化为具有上述性质的数列,需要应用到笛卡尔树,具体过程为在普通数列上构造笛卡尔树,在笛卡尔树上使用欧拉路径转化的方法将树转化为具有上述性质的新数列。
3、范围最值查询问题也可以解释为二维范围查询问题,或者三边范围查询问题,笛卡尔平面上的有限点集可以用来构造笛卡尔树,首先将这些点按照x取值排序,然后将y值作为数列中元素的值,以此数列建立笛卡尔树。若S为有限点集中满足 条件的点集,设p是S中x值最小的点,q是S中x值最大的点,则笛卡尔树中p与q的最低公共祖先即为该点集中处于该x值范围内y值最高/低的点b。三边范围查询问题,即给定条件,取出所有满足条件的点。其解决是以笛卡尔树找到b点,若b点的y值满足条件,则递归地在p,b所约束的子树以及b,q所约束的子树内重复这一过程,这一查询可以使每个被报告的点都在常数时间内找到,总体的时间复杂度为,k即为满足条件的点数。
4、笛卡尔树同样可以应用于以常数时间查询超度量空间内点对的距离。超度量空间内距离的定义与最宽路径问题中的权重相同。从最小生成树上可以构造一个笛卡尔树,根结点表示最小生成树中的权值最大的边,撤去此边会将最小生成树分割为两个子树,笛卡尔树递归地从这两棵子树上构造。笛卡尔树的叶结点表示度量空间内的点,两个叶结点的最低公共祖先则是这两个点在最小生成树中最重的边,代表这两点间的距离。获得了最小生成树及将边按照权值排序后,笛卡尔树即可在线性时间内构造出来。

感觉笛卡尔树的妙用很多呢,但我们还是得先从模板题做起:

POJ2201:http://poj.org/problem?id=2201

这道题的题意就是,让你构造一颗笛卡尔树,并将其按原来的顺序输出其父亲节点和左右儿子节点

那么笛卡尔树怎么构造呢?

我们选择用单调栈来进行笛卡尔树的生成,单调栈就是为了维护当前树中的最右链

首先我们要将其按key的值进行排序,将其按照key值排序,排序能够保证树按照最右链进行生成。

然后我们

上代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <stack>
const int maxn=5e4+7;
using namespace std;
int n,root,fa[maxn],ls[maxn],rs[maxn];
struct Node{
    int key,val,id;
    bool operator < (const Node &a) const {
        return a.val<val;
    }//重载运算符比重写函数会更快
};
struct ANS{
    int l,r,fa;
}ans[maxn];
Node node[maxn];
stack <Node> ST;
void build(){
    sort(node+1,node+1+n);
    for(int i=1;i<=n;i++){
        Node sp=node[i];
        Node sq;
        bool flag=0;
        while(!ST.empty() && sp.key<ST.top().key){//对于一个节点u,我们从栈顶开始找,找到第一个key小于key[u]的节点v;
            sq=ST.top();
            ST.pop();//这样栈中v之上的节点均不在树的最右边的链上了,所以都将其弹出,并让入u栈。
            flag=1;//代表找到了合适的节点,下面就需要进行父亲儿子的赋值
        }
        if(flag){
            fa[sq.id]=sp.id;
            rs[sp.id]=sq.id;
        }
        //令v的右子树成为的u左子树,u成为v的右儿子;
        if(!ST.empty()){
            fa[sp.id]=ST.top().id;
            ls[ST.top().id]=sp.id;
        }
        else
            fa[sp.id]=0;
        ST.push(sp);//u入栈
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d %d",&node[i].val,&node[i].key);
        node[i].id=i;//记录每一个原来的位置
    }
    build();
    printf("YES\n");
    for(int i=1;i<=n;i++){
        printf("%d %d %d\n",fa[i],ls[i],rs[i]);
    }
    return 0;
}

注释里应该写的都很清楚,有疑问的话,来下方评论区进行思维大碰撞哈!欢迎交流!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值