" /> " /> " />

## 题目链接

[this](https://www.luogu.com.cn/problem/P5854)

># P5854 【模板】笛卡尔树

>

>## 题目描述

>

>给定一个1 \sim n 的排列p,构建其笛卡尔树。

>

>即构建一棵二叉树,满足:

>

>1. 每个节点的编号满足二叉搜索树的性质。

>2. 节点i 的权值为p_i,每个节点的权值满足小根堆的性质。

>

>## 输入格式

>

>第一行一个整数n

>

>第二行一个排列p_{1 \dots n}

>

>## 输出格式

>

>设l_i,r_i 分别表示节点i 的左右儿子的编号(若不存在则为0)。

>

>一行两个整数,分别表示\operatorname{xor}_{i = 1}^n i \times (l_i + 1)\operatorname{xor}_{i = 1}^n i \times (r_i + 1)

>

>## 输入输出样例 #1

>

>### 输入 #1

>

>```

>5

>4 1 3 2 5

>```

>

>### 输出 #1

>

>```

>19 21

>```

>

>## 说明/提示

>

>【样例解释】

>

>|i |l_i |r_i |

>| :-: | :-: | :-: |

>|1 |0 |0 |

>|2 |1 |4 |

>|3 |0 |0 |

>|4 |3 |5 |

>|5 |0 |0 |

>

>【数据范围】

>

>对于30\% 的数据,$n \le 10^3$。

>

>对于60\% 的数据,$n \le 10^5$。

>

>对于80\% 的数据,$n \le 10^6$。

>

>对于90\% 的数据,$n \le 5 \times 10^6$。

>

>对于100\% 的数据,$1 \le n \le 10^7$。

## 题目分析

本题要求构建一个笛卡尔树,满足:

- BST性质:节点编号满足二叉搜索树性质(中序遍历为1,2, ...,n

- 小根堆性质:节点权值满足小根堆性质(父节点权值 子节点权值)

## 算法思路 ###

单调栈构建法

使用单调栈在O(n) 时间内构建笛卡尔树。

核心思想是维护一个**单调递增栈**,栈中存储的是从根到最右节点的路径(右链)。

### 构建过程

按照节点编号1n 的顺序逐个插入节点:

1. 弹栈操作:弹出栈中所有权值大于p[i] 的节点

- 这些节点由于小根堆性质,不能成为节点i 的祖先

- 记录最后一个弹出的节点lst

2. 建立父子关系:

- 若栈不为空,节点i 成为栈顶的右儿子

- 若lst 存在,它成为节点i 的左儿子

3. 入栈:将节点i 压入栈中

## 代码实现

```cpp

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

inline LL read() {

LL x = 0, f = 1;

char ch = getchar();

while (ch > '9' || ch < '0') {

if (ch == '-') f = -f;

ch = getchar();

}

while (ch >= '0' && ch <= '9') {

x = x * 10 + ch - '0';

ch = getchar();

}

return x * f;

}

inline void write(LL x) {

if (x < 0) {

putchar('-');

x = -x;

}

if (x > 9) write(x / 10);

putchar(x % 10 + '0');

}

const int MAXN = 1e7 + 5;

LL p[MAXN], L[MAXN], R[MAXN], stk[MAXN], top;

int main() {

int N = read();

// 读入权值数组

for (int i = 1; i <= N; ++i) {

p[i] = read();

}

// 单调栈构建笛卡尔树

for (int i = 1; i <= N; ++i) {

int lst = 0;

// 弹出所有权值大于 p[i] 的节点

while(top > 0 && p[stk[top]] > p[i])

lst = stk[top--];

// i 成为栈顶的右儿子

if (top > 0)

R[stk[top]] = i;

// 最后弹出的节点成为 i 的左儿子

if (lst > 0)

L[i] = lst;

// 将 i 压入栈

stk[++top] = i;

}

// 计算答案

LL ans1 = 0, ans2 = 0;

for (int i = 1; i <= N; ++i) {

ans1 ^= i * (L[i] + 1);

ans2 ^= i * (R[i] + 1);

}

write(ans1), putchar(' '), write(ans2);

return 0;

}

```

## 算法详解

### 为什么这样构建是正确的?

1. BST性质保证:由于我们按照1n 的顺序插入,每个新节点都在当前树的最右侧,保证了中序遍历的顺序。

2. 小根堆性质保证:通过弹栈操作,确保栈中维护的是一条权值单调递增的路径,从而保证了小根堆性质。

### 样例模拟

以样例p = [4, 1, 3, 2, 5] 为例:

| 步骤 | 操作 | 栈状态 | 左儿子 | 右儿子 |

|------|------|--------|--------|--------|

|i = 1 | 压入1 |[1] |L[1] = 0 |- |

|i = 2 | 弹出1,压入2 |[2] |L[2] = 1 |R[2] = ? |

|i = 3 | 压入3 |[2,3] |L[3] = 0 |R[2] = 3 |

|i = 4 | 弹出3,压入4 |[2,4] |L[4] = 3 |R[2] = 4 |

|i = 5 | 压入5 |[2,4,5] |L[5] = 0 |R[4] = 5 |

最终树结构:

```

2(权值1)

/ \

1 4(权值2)

/ \

3 5

```

## 复杂度分析

- 时间复杂度: $O(n)$

- 每个节点最多入栈一次、出栈一次

- 总操作次数为O(n) - 空间复杂度: $O(n)$

- 需要存储权值数组、左右儿子数组和栈

## 总结

单调栈构建笛卡尔树是一个经典算法,通过维护右链的单调性,巧妙地在线性时间内完成了树的构建。这种方法不仅高效,而且代码简洁优雅,是处理大规模数据的必选方案。

是飘渺的希望 我们依然前行