左偏树

在做一道题的时候遇到了就顺手学了

罗马游戏

刚开始打的线段树合并,结果空间直接炸裂,然后尝试玄学得了60分

后面看标签才知道要用左偏树.

左偏树满足下面几个性质

1.根节点的 \(val\) 小于儿子节点的 \(val\)

2.左儿子到叶子结点的 \(dis\) 大于等于右儿子的 \(dis\)

3.根节点的 \(dis\) 等于右儿子的 \(dis + 1\)

以及一些其他关于树的大小的证明

然后是左偏树的一些操作

合并操作

int merge(int x, int y)
{
	if (!x || !y)
		return x + y;
	if (tr[x].v > tr[y].v)
		swap(x, y);
	rs = merge(rs, y);
	if (tr[ls].dis < tr[rs].dis)
		swap(ls, rs);
	tr[ls].rt = tr[rs].rt = tr[x].rt = x;
	tr[x].dis = tr[rs].dis + 1;
	return x;
}

\(x\) 作为根节点, \(y\) 连到 \(x\) 的右儿子上面,如果右儿子的 \(dis\)更大,交换左右儿子

删除操作

void pop(int x)
{
	tr[x].v = -1;
	tr[ls].rt = ls, tr[rs].rt = rs;
	tr[x].rt = merge(ls, rs);
}

删除根节点

获取根节点

int get(int x)
{
	return tr[x].rt == x ? x : tr[x].rt = get(tr[x].rt);
}

然后是完整代码

#include<iostream>
#include<cstdio>
#define ls tr[x].l
#define rs tr[x].r
using namespace std;
const int N = 1e6 + 5;

struct tree
{
	int l, r, rt, v, dis;
} tr[N];

int merge(int x, int y)
{
	if (!x || !y)
		return x + y;
	if (tr[x].v > tr[y].v)
		swap(x, y);
	rs = merge(rs, y);
	if (tr[ls].dis < tr[rs].dis)
		swap(ls, rs);
	tr[ls].rt = tr[rs].rt = tr[x].rt = x;
	tr[x].dis = tr[rs].dis + 1;
	return x;
}

int get(int x)
{
	return tr[x].rt == x ? x : tr[x].rt = get(tr[x].rt);
}

void pop(int x)
{
	tr[x].v = -1;
	tr[ls].rt = ls, tr[rs].rt = rs;
	tr[x].rt = merge(ls, rs);
}

int main()
{
	int n, m;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &tr[i].v), tr[i].rt = i;
	scanf("%d", &m);
	while (m--)
	{
		char opt;
		int x, y;
		scanf(" %c", &opt);
		if (opt == 'M')
		{
			scanf("%d%d", &x, &y);
			if (tr[x].v == -1 || tr[y].v == -1)
				continue;
			int r1 = get(x), r2 = get(y);
			if (r1 != r2)
				tr[r1].rt = tr[r2].rt= merge(r1, r2);
		}
		else 
		{
			scanf("%d", &x);
			if (tr[x].v == -1)
				puts("0");
			else 
				printf("%d\n", tr[get(x)].v), pop(get(x));
		}
	}
	return 0;
}
posted @ 2021-08-14 17:11  DSHUAIB  阅读(38)  评论(0编辑  收藏  举报