笛卡尔树学习笔记

备战初赛 发现21年补全代码最后一道出的笛卡尔树 结果发现我忘了 于是来复习一下


定义

  • 一棵二叉树

  • 节点编号满足二叉搜索树性质

  • 节点权值满足小根堆性质


构建

我们考虑按顺序插入一个数
那么假如说我插入当前的数 它一定是当前树上所有点中编号最大的点 所以它理应插入这个树的最右下角的右儿子

同时我们要满足小根堆性质 如果它大于等于它的父节点 那么这很好
但是事情总不会一直都很好的 所以我们考虑如果它小于它的父节点 那它就要向上走
那么原来它的父节点就会成为它的左儿子(因为二叉搜索树)
重复此过程(可以自己画个图看一下)

进而我们发现 实际上我们就是找它从这个树最右下角一直往上走第一个权值小于它的位置
然后把原来底下的都作为它的左儿子
这个过程就可以用一个单调栈维护

for (int i = 1; i <= n; ++i) {
	int k = top;
	while (k > 0 && a[stk[k]] > a[i]) --k; //找到第一个比它小的位置 
	if (k) rs[stk[k]] = i;
	if (k < top) ls[i] = stk[k + 1];
	stk[++k] = i;
	top = k;
}

特别强调

以下这个写法是错的!

for (int i = 1; i <= n; ++i) {
	while (top && a[i] < a[stk[top]]) --top;
	rs[stk[top]] = i;
	ls[i] = stk[top + 1];
	stk[++top] = i;
}

原因在于 当你弹栈的时候不是真的把没用的都清掉了 只是把 top 指向的位置改变了而已
然后你没有判 top + 1 是否没被弹掉 就导致可能存在正常做 top + 1 是空的但是实际上是个奇奇怪怪的应该被弹掉的东西
然后就 WA 掉了


P5854 【模板】笛卡尔树

板子题

#include <bits/stdc++.h>
#define ll long long
using namespace std;

namespace steven24 {
	const int N = 1e7 + 0721;
	int a[N], stk[N], ls[N], rs[N];
	int n, top;
	ll ansl, ansr;
	
	inline int read() {
		int xr = 0, F = 1;
		char cr;
		while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
		while (cr >= '0' && cr <= '9')
			xr = (xr << 1) + (xr << 3) + (cr ^ 48), cr = getchar();
		return xr * F;
	}
	
	void main() {
		n = read();
		for (int i = 1; i <= n; ++i) a[i] = read();
		
		for (int i = 1; i <= n; ++i) {
			int k = top;
			while (k > 0 && a[stk[k]] > a[i]) --k; //找到第一个比它小的位置 
			if (k) rs[stk[k]] = i;
			if (k < top) ls[i] = stk[k + 1];
			stk[++k] = i;
			top = k;
		}
		
		for (int i = 1; i <= n; ++i) {
			ansl ^= (1ll * i * (ls[i] + 1));
			ansr ^= (1ll * i * (rs[i] + 1));
		}
		
		printf("%lld %lld\n", ansl, ansr);
	}
}

int main() {
	steven24::main();
	return 0;
}

posted @ 2023-09-01 18:10  Steven24  阅读(7)  评论(0编辑  收藏  举报