【题目来源】
https://www.luogu.com.cn/problem/P5854
【题目描述】
给定一个 1∼n 的排列 p,构建其笛卡尔树。
即构建一棵二叉树,满足:
1.每个节点的编号满足二叉搜索树的性质。← 优先级 pri 满足二叉搜索树(BST)的性质
2.节点 i 的权值为 pi,每个节点的权值满足小根堆的性质。← 键值 val 满足堆的性质
【输入格式】
第一行一个整数 n。
第二行一个排列 p1,…,pn。
【输出格式】
设 li,ri 分别表示节点 i 的左右儿子的编号(若不存在则为 0)。
一行两个整数,分别表示 和 。
【输入样例】
5
4 1 3 2 5
【输出样例】
19 21
【样例解释】
i | li | ri |
---|---|---|
1 | 0 | 0 |
2 | 1 | 4 |
3 | 0 | 0 |
4 | 3 | 5 |
5 | 5 | 5 |
【数据范围】
对于 30% 的数据,n≤10^3。
对于 60% 的数据,n≤10^5。
对于 80% 的数据,n≤10^6。
对于 90% 的数据,n≤5×10^6。
对于 100% 的数据,1≤n≤10^7。
【算法分析】
(一)笛卡尔树
● 笛卡尔树是一种非常特殊的二叉搜索树(BST)。每个结点有两个信息 (pri, val),如果只考虑 pri,它是一棵二叉搜索树,如果只考虑 val,它是一个小根堆。
● 一个有趣的事实是,如果笛卡尔树的 pri 值互不相同,val 值互不相同,那么这个笛卡尔树的结构是唯一的。如来源于 https://oi-wiki.org/ds/cartesian-tree/ 的图示如下。其中,此图中的数字是笛卡尔树的 val 值,各数字所在数组对应的下标是笛卡尔树的 pri 值。例如,在图中9对应的下标为1,3对应的下标为2,7对应的下标为3,1对应的下标为4,……,5对应的下标为11。
● 构造笛卡尔树时,若各个结点指定了 pri,则先对 pri 从小到大排列。否则,pri 默认为数组下标。在保证 pri 递增的情况下,可以在线性时间复杂度内构造一棵笛卡尔树。
● 构建笛卡尔树的过程:由于已对结点信息 (pri, val) 中 pri 递增排序,所以依据 pri 将结点逐个插入到笛卡尔树中时,为了符合“如果只考虑 pri,它是一棵二叉搜索树”的性质,即满足“左小右大”的性质,那么每次新插入的结点 cur 必然在笛卡尔树的右链的末端。(右链:即从笛卡尔树的根结点一直往右子树走,所经过的结点形成的路径。)
● 之后,从下往上比较笛卡尔树右链结点与右链末端结点 cur 的 val 值。如果找到了一个右链上的结点 x 满足 ,为了满足“如果只考虑 val,它是一个小根堆”的性质,就把 cur 接到 x 的右儿子上,而 x 原本的右子树就变成 cur 的左子树。
● Treap 本质上也是一种笛卡尔树。
(二)快读
● 快读:https://blog.csdn.net/hnjzsyjyj/article/details/120131534
在数据量比较大的时候,即使使用 scanf 函数读入数据也会超时,这时就需要使用“快读”函数了。“快读”函数之所以快,是因为其中的 getchar 函数要比 scanf 函数快。 网传,“快读”函数能以比 scanf 函数快5倍的速度读入任意整数。
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;
}
【算法代码】
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1e7+5;
int a[maxn];
int n;
LL lson[maxn],rson[maxn];
LL ans1,ans2;
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;
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
a[i]=read();
while(!s.empty() && a[s.top()]>a[i]) {
lson[i]=s.top();
s.pop();
}
if(!s.empty()) rson[s.top()]=i;
s.push(i);
}
for(int i=1; i<=n; i++) {
ans1=ans1^(i*(lson[i]+1));
ans2=ans2^(i*(rson[i]+1));
}
printf("%lld %lld",ans1,ans2);
return 0;
}
/*
in:
5
4 1 3 2 5
out:
19 21
*/
【参考文献】
https://blog.csdn.net/Code92007/article/details/94591571
https://oi-wiki.org/ds/cartesian-tree/
https://www.luogu.com.cn/problem/solution/P5854
https://zhuanlan.zhihu.com/p/674774129
https://www.cnblogs.com/reverymoon/p/9525764.html