在做一道题的时候遇到了就顺手学了
罗马游戏
刚开始打的线段树合并,结果空间直接炸裂,然后尝试玄学得了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; }
|