首页 > 其他分享 >笛卡尔树

笛卡尔树

时间:2024-07-23 15:22:43浏览次数:13  
标签:ch 笛卡尔 int top stk 节点

\(\texttt{0x00}\):前置芝士

  1. 二叉搜索树
  2. 单调栈

\(\texttt{0x01}\):概念

笛卡尔树是一种二叉树,每一个结点由一个键值二元组 \((k,w)\) 构成。要求 \(k\) 满足二叉搜索树的性质(左小右大),而 \(w\) 满足堆的性质(大根堆或小根堆)。

q1:这么一看,Treap 不也是笛卡尔树?

a1:正确的。

一个有趣的事实是,如果笛卡尔树的 \(k,w\) 键值确定,且 \(k\) 互不相同,\(w\) 互不相同,那么这个笛卡尔树的结构是唯一的。

q2:那它有什么用,写个 Treap 不是就可以代替它了吗?

a2:它在一些问题上(比如求最大值)的速度比 Treap 快,这就足够了。

\(\texttt{0x02}\):构建

对于一个 \(1\sim n\) 的排列,以下标为键值 \(k\) (满足二叉搜索树性质),数值为键值 \(w\) (满足小根堆的性质),如何建立笛卡尔树?

考虑到笛卡尔树的性质,我们先将整个序列的最小值找出来作为根节点,然后再分别在其左右区间中找到最小值作为左右子树的根节点,依次这样递归下去即可完成建树。

比如一个序列 \(\{3,5,7,1,4,2,6\}\) 它的建树过程如下:

先找到全局最小值 \(1\) 作为根节点。

递归左子树,找到区间 \(\{3,5,7\}\) 中的最小值 \(3\) 作为左子树的根节点。

递归右子树,找到区间 \(\{4,2,6\}\) 中的最小值 \(2\) 作为右子树的根节点。

继续向下递归,找到区间 \(\{5,7\}\) 中的最小值 \(5\) 作为子树根节点。

右子树中左为 \(4\),右为 \(6\)。

\(7\) 挂在 \(5\) 右子树上。

最坏情况下时间复杂度为 \(O(n^2)\),用线段树或 ST 表优化后为 \(O(n\log n)\)。

当然也可以用 Treap 一个一个插入,也是 \(O(n\log n)\)。

P5854 【模板】笛卡尔树

注意数据范围:\(n\le 10^7\)。

Treap 一下就释怀地似了。

这要求我们在线性时间复杂度内完成建树操作,怎么办?

回想一下笛卡尔树的性质,发现下标一定是单调递增的,所以我们只会在右子树上插入新的节点!

考虑一下每个点插入时的情形:

当我们插入一个点 \(i\) 时,实际上就是在右链上找到一个一个位置,使得 \(w_u\) < \(w_i\) < \(w_v\),然后我们将 \(u\) 的右儿子修改成 \(i\),\(i\) 的左儿子修改为 \(v\)。

这启示我们只需要维护这个右链,显然可以用单调栈维护。

若新加的点小于栈顶,则弹出,直到栈为空或栈顶比它大。

注意这里要特判一下,若栈已经为空了,说明当前点要作为根节点,所以要把根节点更新,并且不用将栈顶的右儿子修改为 \(i\)。

用插入的方式理解一下上述例子:

其中蓝色框起来的部分就是维护的右链。

\(\texttt{Code}\):

#include <iostream>
using namespace std;
const int N = 10000010;
int n;
int a[N];
int stk[N], top;
struct node{
    int ls, rs;
}tr[N];
int root;

inline int read() {
    int x = 0;
    char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - 48; ch = getchar();}
    return x;
}

void build() {
    for(int i = 1; i <= n; i++) {
        int las = 0;
        while(top > 0 && a[stk[top]] >= a[i]) las = stk[top--];
        if(!top) root = i;
        if(top) tr[stk[top]].rs = i;
        tr[i].ls = las;
        stk[++top] = i;
    }
}

int main() {
    n = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    build();
    long long res1 = 0, res2 = 0;
    for(int i = 1; i <= n; i++)
        res1 ^= 1ll * i * (tr[i].ls + 1), res2 ^= 1ll * i * (tr[i].rs + 1);
    printf("%lld %lld\n", res1, res2);
    return 0;
}

P1377 [TJOI2011] 树的序

题目大意:

根据生成序列建出一棵笛卡尔树,求一个字典序最小且和它能得到相同笛卡尔树的生成序列。

思路:

先解释一下为什么是建出一颗笛卡尔树。

首先键值 \(k\) 满足 BST 性质,其次它是一个一个插入到子树里的,所以如果建立一个时间戳,父节点的时间戳一定小与子节点,这就满足堆性质,可以把时间戳当成笛卡尔树中的第二个键值,所以是建一棵笛卡尔树。

所以接下来思路就很明显了,我们先建一棵小根堆笛卡尔树,由于要字典序最小,所以我们得把尽量小的点往左子树里塞,具体操作就是对该笛卡尔树进行先序遍历,直接输出遍历到的每个点即可。

只需要在模板题的代码上加一个 dfs 就行了。

时间复杂度为 \(O(n)\)。

\(\texttt{Code}\):

#include <iostream>

using namespace std;

const int N = 100010;

int n;
int a[N];
int stk[N], top;
struct node{
    int ls, rs;
}tr[N];
int root;

inline int read() {
    int x = 0;
    char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - 48; ch = getchar();}
    return x;
}

void dfs(int u) {
    printf("%d ", u);
    if(tr[u].ls) dfs(tr[u].ls);
    if(tr[u].rs) dfs(tr[u].rs);
}

void build() {
    for(int i = 1; i <= n; i++) {
        int las = 0;
        while(top > 0 && a[stk[top]] >= a[i]) las = stk[top--];
        if(!top) root = i;
        if(top) tr[stk[top]].rs = i;
        tr[i].ls = las;
        stk[++top] = i;
    }
    dfs(root);
}

int main() {
    n = read();
    for(int i = 1; i <= n; i++) a[read()] = i;
    build();
    return 0;
}

标签:ch,笛卡尔,int,top,stk,节点
From: https://www.cnblogs.com/Brilliant11001/p/18318481

相关文章

  • 仅产生 2D 笛卡尔积的上三角形
    我知道如何使用itertools.productIn[19]:fromitertoolsimportproduct...:...:abcd=product('abcd','abcd')...:foriinrange(4):...:print(''.join(repr(t)for_,tinzip(range(4),abcd)))......
  • Franka Robot 如何理解机器人的笛卡尔阻抗运动
    在笛卡尔阻抗模式下,用手将机器人移动到一个新位置后,机器人的行为取决于其控制参数(刚度、阻尼、质量)的设定和外部力的作用。当你将机器人移动到一个新位置并释放它时,以下是可能的情况:高刚度情况下如果机器人的刚度(Stiffness)参数设置较高,意味着机器人对位置偏差有很强的恢复力。当......
  • 笛卡尔树
    笛卡尔树基本概念笛卡尔树是基于一个静态序列\(a\)的,根据这个序列\(a\),我们可以构造出对应的笛卡尔树。笛卡尔树有三点要求需要满足:笛卡尔树是二叉树。笛卡尔树的编号的中序遍历为\(1\simn\),权值中序遍历为\(a\)。笛卡尔树的权值满足大根堆或者小根堆的性质。......
  • Franka 内部关节阻抗控制器和内部笛卡尔阻抗控制器的区别
    Franka机器人内部的关节阻抗控制器和笛卡尔阻抗控制器之间的本质区别如下:1.控制空间关节空间vs.笛卡尔空间:关节阻抗控制器工作在关节空间,即以关节角度、关节速度和关节扭矩为控制变量。笛卡尔阻抗控制器工作在笛卡尔空间,即以末端执行器的位置、速度和力作为控制变量。......
  • Franka libfranka 基于笛卡尔空间位置控制
    #include<array>#include<cmath>#include<iostream>#include<franka/exception.h>#include<franka/model.h>#include<franka/robot.h>#include<franka/tools.h>intmain(intargc,char**argv){try{//......
  • Franka libfranka 基于笛卡尔空间位置的运动控制
    #include<array>#include<cmath>#include<iostream>#include<franka/exception.h>#include<franka/model.h>#include<franka/robot.h>#include<franka/tools.h>intmain(intargc,char**argv){try{//......
  • P5854 【模板】笛卡尔树
    原题链接题解笛卡尔树的定义如下:任意一颗子树都代表一段连续的区间,且子树的根节点是该区间的最大值,根的左边的元素下标均比根小(二叉搜索树性质),子节点均比父节点大(堆的性质)我们讲如何实现的设即将要插入的元素为\(a_i\)栈内的元素为前\(i-1\)个元素构成的笛卡尔树从根一直......
  • 笛卡尔树
    笛卡尔树对于每个区间,找到区间的极值,把这个区间一分为二,这个极值就是这个区间的根,两个部分的根是极值的两个儿子如何求笛卡尔树?以大根堆为例方法一令\(l_i\)表示第\(i\)个元素向左第一个\(\ge\)它的位置,\(r_i\)表示第\(i\)个元素向右第一个\(\ge\)它......
  • 笛卡尔树
    引入首先我们看到这个序列\([9,4,10,1,7,2,3]\),现在我们找到它的最大值\(10\),并从中间劈开,此时分为了两个序列\([9,4]\)和\([1,7,2,3]\),接着对这两个序列继续这样的操作。现在,将劈开后序列最大值和被劈开的数建立父子关系,于是便建立了这个树:这也就是笛卡尔树。笛卡尔树满......
  • 基环树和笛卡尔树
    基环树就是比平常的树多一条边,有\(n\)条边,也就有一个环在里面。基本思想就是断环,跑树形\(dp\),或者用拓扑排序判环去跑环形\(dp\)。树的直径今天才了解到的,用两遍\(dfs\)跑。首先第一遍找到离根节点最远的节点\(u_1\),然后再从\(u_1\)找到离它最远的节点\(u_2\),那么......