笛卡尔树笔记

笛卡尔树笔记

【模板】笛卡尔树

题目描述

给定一个 \(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\)


思路

以当前为根,从左往右走,如果能放在当前右子树,否则就一直往上找,直到找到当前的权值 \(p_i\) 比当前点的权值 \(p_i\) 大,则符合条件,放在其右子树。对于向上找时跳过的不符合条件的点,则将其放在当前接入点的左儿子。
那么,这样子的复杂度就是 \(O(N^2)\) 。考虑将向上遍历直到找到当前的权值 \(p_i\) 比当前点的权值 \(p_i\) 大这一步骤优化,即找到先前序列中位于该位置之前的以一个大于该值的树。由此想到了单调栈。
所以做法大致是“贪心 \(+\) 单调栈”。

code

注意要输入解绑,不然会 \(TLE\) (实践出真知)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e7+5;
int st[N];
int a[N];
int n;
int l[N],r[N];
void build(){
	int top=0,cur=0;
	for(int i=1;i<=n;i++){
		while(cur&&a[st[cur]]>a[i]) cur--;
		//cur:插在某个结点的右子树上
		if(cur) r[st[cur]]=i;
		//cur<top:插在某个结点的左子树上
		if(cur<top) l[i]=st[cur+1];
		st[++cur]=i;
		top=cur;
	}
}
signed main(){
	ios::sync_with_stdio(NULL);
	cin.tie(NULL);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	build();
	int ans=0;
	for(int i=1;i<=n;i++) ans^=1LL*i*(l[i]+1);
	cout<<ans<<" ";
	ans=0;
	for(int i=1;i<=n;i++) ans^=1LL*i*(r[i]+1);
	cout<<ans<<endl;
	return 0;
}

posted @ 2024-12-20 21:58  Qian·JXのjoker  阅读(47)  评论(0)    收藏  举报