洛谷 P1377 [TJOI2011]:树的序 ← 笛卡尔树

【题目来源】
https://www.luogu.com.cn/problem/P1377

【题目描述】
众所周知,二叉查找树的形态和键值的插入顺序密切相关。准确的讲:
1.空树中加入一个键值 k,则变为只有一个结点的二叉查找树,此结点的键值即为 k。
2.在非空树中插入一个键值 k,若 k 小于其根的键值,则在其左子树中插入 k,否则在其右子树中插入 k。
我们将一棵二叉查找树的键值插入序列称为
树的生成序列现给出一个生成序列,求与其生成同样二叉查找树的所有生成序列中字典序最小的那个,其中,字典序关系是指对两个长度同为 n 的生成序列,先比较第一个插入键值,再比较第二个,依此类推。

【输入格式】
第一行,一个整数 n,表示二叉查找树的结点个数。
第二行,有 n 个正整数 k1,k2,⋯,kn,表示生成序列,简单起见,k1∼kn 为一个 1 到 n 的排列。

【输出格式】
一行,n 个正整数,为能够生成同样二叉查找树的所有生成序列中最小的。

【输入样例】
4
1 3 4 2

【输出样例】
1 3 2 4

【数据范围及约定】
对于 20% 的数据,1≤n≤10。
对于 50% 的数据,1≤n≤100。
对于 100% 的数据,1≤n≤10^5。

【算法分析】
● 题目中所提及到的“现给出一个生成序列,求与其生成同样二叉查找树的所有生成序列中字典序最小的那个”是一个必须理清的关键。意思是说,存在多个序列,能生成同样的二叉查找树,需输出字典序最小的那个。如本题样例中,1 3 4 2 与 1 3 2 4 都能够生成同样的二叉查找树,但 1 3 2 4 的字典序更小,所以输出了 1 3 2 4。
那么本题如何保证输出字典序最小的序列呢?由题设的构建条件知,所构建的二叉查找树的所有子树结点值相对于根结点而言都满足“
左小右大”的规则,因此对其进行先序遍历便能得到字典序最小的序列。
例如,按照题设规则,即“左小右大”规则,对本题样例 1,3,4,2 构建生成的二叉查找树如下所示。
然后,对此图进行
先序遍历,可得到字典序最小的输出 1,3,2,4。

● 本题可采用笛卡尔树进行求解。笛卡尔树结构由 Vuillmin(1980) 在解决范围搜索的几何数据结构问题时提出。笛卡尔树是一种非常特殊的二叉查找树(BST)。每个结点有两个信息 (pri, val),如果只考虑 pri,它是一棵二叉查找树,如果只考虑 val,它是一个小根堆。其中,pri 是结点插入顺序,或称优先级。val 是结点的值。也就是说,笛卡尔树具有二叉查找树和堆的双重特性

● 如何构建笛卡尔树?(参考于:https://wansuanfa.com/index.php/776
笛卡尔树的构建步骤如下:(构建前需对 pri 递增排序,或选择数组元素的默认递增下标)
(1)用数组中的第一个元素创建根结点。
(2)遍历数组中剩下的元素,如果新元素比根结点小,让根结点成为新结点的左子结点,然后新结点成为根结点。
(3)如果新结点比根结点大,就从根结点沿着最右边这条路径一种往右走,直到找到比新结点大的结点 node ,也可能没找到。如果找到,就让新结点替换 node 结点的位置,让 node 结点变成新结点的左子结点。如果没找到,就让新结点成为最后查找的那个结点的右子结点。
(4)重复上面步骤(2),(3),直到元素全部遍历完。

● 构建笛卡尔树的关键点解析(参考于:
https://wansuanfa.com/index.php/776
(1)如果新结点比根结点小,根据小根堆(元素的值满足小根堆)的性质,那么新结点一定是根结点的父节点。又因为根结点的下标比新结点小,根据二叉查找树(元素的下标满足二叉查找树)的性质,根结点只能是新结点的左子树。
(2)如果新结点比根结点大,那么新结点只能往右边查找,不能往左边,如果往左边就不满足二叉查找树的性质了。如果比右子结点还大,就继续往右,所以只要比右子结点大就会一直往右。如果比右子结点小,根据小根堆的性质,新结点就会成为这个右子结点的父结点,根据二叉查找树的性质,这个右子结点要成为新结点的左子结点(因为右子结点的下标比新结点小)。

下图为构建笛卡尔树的过程示意图。通过构建过程,易知如何维护笛卡尔树每个结点的两个信息 
(pri, val),以及如何保障笛卡尔树具有二叉查找树和堆的双重特性。由图可知,构建方法为以 val 值作为结点,并用其构建小根堆。当需要调整时,以结点插入顺序(或称优先级 pri)为依据,将对应结点置于左子树或右子树,从而保证“如果只考虑 pri,它是一棵二叉查找树”的特性。
简言之,一棵笛卡尔树,结点的值为 val,结点的位置为 pri 所致。



● 单调栈:https://blog.csdn.net/hnjzsyjyj/article/details/117370314
单调栈是一种非常适合处理“下一个更大元素(Next Greater Number)”问题的数据结构。
笛卡尔树的构建过程中,常还需借助“
单调栈”这种数据结构,以保证在线性时间内完成笛卡尔树的构建。单调栈中保存的始终是笛卡尔树的右链,即由笛卡尔树的“根结点、根结点的右儿子、根结点的右儿子的右儿子、……”组成的链。并且,为了维护笛卡尔树的小根堆特性,单调栈中的元素值从栈顶到栈底需依次递减,且栈底存储的是根结点。由于每个元素最多进出站各一次,所以时间复杂度为 O(n)。

● 快读:https://blog.csdn.net/hnjzsyjyj/article/details/120131534
在数据量比较大的时候,即使使用 scanf 函数读入数据也会超时,这时就需要使用“快读”函数了。“快读”函数之所以快,是因为其中的
getchar 函数要比 scanf 函数快。 网传,“快读”函数能以比 scanf 函数快5倍的速度读入任意整数
貌似,笛卡尔树问题常用到快读。

● 本题代码与 https://blog.csdn.net/hnjzsyjyj/article/details/138353654 及其相似,可对比学习。

【算法代码】

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int maxn=1e5+5;

int a[maxn];
int n;
LL lson[maxn],rson[maxn];

stack<int> s;

int read() { //fast read
    int x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9') { //!isdigit(c)
        if(c=='-') f=-1; //- is a minus sign
        c=getchar();
    }
    while(c>='0' && c<='9') { //isdigit(c)
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}

void print(int x) {
    if(x==0) return;
    cout<<x<<" ";
    print(lson[x]);
    print(rson[x]);
}

int main() {
    scanf("%d",&n);
    for(int i=1; i<=n; i++) {
        int id;
        id=read();
        a[id]=i;
    }

    for(int i=1; i<=n; i++) {
        while(!s.empty() && a[s.top()]>a[i]) {
            lson[i]=s.top();
            s.pop();
        }
        if(!s.empty()) rson[s.top()]=i;
        s.push(i);
    }

    int root;
    while(!s.empty()) {
        root=s.top();
        s.pop();
    }

    print(root);

    return 0;
}

/*
in:
4
1 3 4 2

out:
1 3 2 4
*/




【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/138353654
https://blog.csdn.net/hnjzsyjyj/article/details/138353654
https://blog.csdn.net/liufengwei1/article/details/107866930
https://www.cnblogs.com/LSlzf/p/11150484.html
https://www.cnblogs.com/yinyuqin/p/15000037.html
https://zhuanlan.zhihu.com/p/674774129
https://wansuanfa.com/index.php/776
https://blog.csdn.net/pi9nc/article/details/9388761
 

  • 47
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值