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

​罗马游戏​

刚开始打的线段树合并,结果空间直接炸裂,然后尝试玄学得了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;
}