洛谷 P5854:【模板】笛卡尔树

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

【题目描述】
给定一个 1∼n 的排列 p,构建其笛卡尔树。
即构建一棵二叉树,满足:
1.每个节点的编号满足二叉搜索树的性质。← 优先级 
pri 满足二叉搜索树(BST)的性质
2.节点 i 的权值为 pi,每个节点的权值满足小根堆的性质。← 键值 val 满足的性质

【输入格式】
第一行一个整数 n。
第二行一个排列 p1,…,pn。

【输出格式】
设 li,ri 分别表示节点 i 的左右儿子的编号(若不存在则为 0)。
一行两个整数,分别表示 xor_{i=1}^{n}i\times (l_i+1) 和 xor_{i=1}^{n}i\times (r_i+1)

【输入样例】
5
4 1 3 2 5

【输出样例】
19 21


【样例解释】​​​​​​​

ili​ri​
100
214
300
435
555


【数据范围】
对于 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 满足 x_{val}< cur_{val},为了满足“
如果只考虑 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









 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值