参考资料
http://www.yhzq-blog.cc/fhq-treap%e6%80%bb%e7%bb%93/
实现基本照抄 kactl
感觉有必要,就顺手学一下好了

笛卡尔树

定义 一棵二叉树,节点维护键值对 (key, val),其中 key 满足堆性质而 val 满足二叉树性质
Treap 即为一棵特殊的笛卡尔树

Treap

结构 Treap为一棵笛卡尔树,其中 val 为所维护的值,而 key 为随机赋予的
由随机堆的性质,这棵树的深度为 O(logn) 级别的
维护 容易考虑用传统的旋转来维护,但是可以用分裂与合并操作来替代,其代码更短
需要维护的值具体有:key, val, cnt, l, r

class Node {
 public:
    int a, y, c = 1;    //val, key, cnt
    Node *l = 0, *r = 0;
    Node(int val) : a(val), y(rnd()) {}
    void recalc() { c = cnt(l) + cnt(r) + 1; }
};

其中随机值推荐使用 mt19937,简单测了一下发现比自己写的线性同余还快(rand()可以爬了)

FHQ-Treap

核心操作

分离 Split 将原树中的前 k 大的节点分裂出去
考虑一个递归过程
要从节点 u 的子树中分出 k 个节点
若左子树数量大于等于 k ,则变为从左子树中分出 k 个节点
否则从右子树中分出 k - cnt(l) - 1 个节点,接到当前节点的右侧,右子树剩下的部分就剩下

//first为被分出去的根,second为剩下部分的根
pair<Node*, Node*> split(Node* u, int k) {
    if (!u) return {};
    if (cnt(u->l) >= k) {
        auto pa = split(u->l, k);
        u->l = pa.second;
        u->recalc();
        return {pa.first, u};
    } else {
        auto pa = split(u->r, k - cnt(u->l) - 1);
        u->r = pa.first;
        u->recalc();
        return {u, pa.second};
    }
}

右合并 Merge 将给定的第二棵树合并到第一棵树的右侧
这里的 即为保证前者所有节点的权小于后者的(这里考虑从小到大)
需要注意该操作是不满足交换律的
还是递归处理
要满足BST性质,后者应当放到前者的右侧,或者前者应放到后者的左侧
假如那一侧已经被占了,那就将那一侧与要放的继续合并
再考虑满足堆性质(这里考虑大根堆)

//返回合并后的根
Node* merge(Node* u, Node* v) {
    if (!u) return v;
    if (!v) return u;
    if (u->y > v->y) {
        u->r = merge(u->r, v);
        u->recalc();
        return u;
    } else {
        v->l = merge(u, v->l);
        v->recalc();
        return v;
    }
}

基本操作的拓展

插入 insert 将某棵树插入到另一颗树的第 k + 1 个位置
做法非常简单,先将前 k 个元素拆出来,然后从左至右依次合并即可

Node* insert(Node* u, Node* v, int k) {
    auto pa = split(u, k);
    return merge(merge(pa.first, v), pa.second);
}

BST的操作

查询排名 rnk
按照BST的方法去做就好了

int rnk(Node* u, int val) {
    if (!u) return 0;
    if (u->a < val)
        return cnt(u->l) + 1 + rnd(u->r, val);
    else
        return rnd(u->l, val);
}

排名查元素 kth
可以偷懒,split两次之后merge

int kth(int k) {
    auto pa = split(rt, k);
    auto pb = split(pa.first, k - 1);
    int ret = pb.second->a;
    merge(merge(pb.first, pb.second), pa.second);
    return ret;
}

插入元素 push
结合rnd与insert就变成了push

void push(int val) {
    nodes[n] = Node(val);
    rt = n ? insert(rt, nodes + n, rnk(rt, val)) : nodes;
    n += 1;
}

弹出元素 pop
偷懒,用split和rnk拆出比它小的,然后剩下部分再拆掉第一个

void pop(int val) {
    auto pa = split(rt, rnk(rt, val));
    rt = merge(pa.first, split(pa.second, 1).second);
}

求前驱 pre 与 求后继 suc
偷懒,还是求个rnk然后拆

int pre(int val) {
    int k = rnk(rt, val);
    auto pa = split(rt, k);
    auto pb = split(pa.first, k - 1);
    int ret = pb.second->a;
    rt = merge(merge(pb.first, pb.second), pa.second);
    return ret;
}
int suc(int val) {
    int k = rnk(rt, val + 1);
    auto pa = split(rt, k);
    auto pb = split(pa.second, 1);
    int ret = pb.first->a;
    rt = merge(pa.first, merge(pb.first, pb.second));
    return ret;
}

另外,容易发现,按照上面那么做,就能处理重复元素的情况
结合上述内容,模板题就搞定了

洛谷P6136 【模板】普通平衡树(数据加强版)

#include <iostream>
#include <random>
using namespace std;

const int MAX_N = 1E6 + 1E5 + 100;

mt19937 rnd(114514);

class Treap {
private:
    class Node {
    public:
        int a, y, c = 1;
        Node *l = 0, *r = 0;
        Node(int val = 0) : a(val), y(rnd()) {}
        void recalc() { c = cnt(l) + cnt(r) + 1; }
    };
    //=============

    static int cnt(Node* u) { return u ? u->c : 0; }

    //============

    int n;
    Node* rt;
    Node nodes[MAX_N];

    //============

    pair<Node*, Node*>
    split(Node* u, int k) {
        if (!u) return {};
        if (cnt(u->l) >= k) {
            auto pa = split(u->l, k);
            u->l = pa.second;
            u->recalc();
            return {pa.first, u};
        } else {
            auto pa = split(u->r, k - cnt(u->l) - 1);
            u->r = pa.first;
            u->recalc();
            return {u, pa.second};
        }
    }

    Node* merge(Node* u, Node* v) {
        if (!u) return v;
        if (!v) return u;
        if (u->y > v->y) {
            u->r = merge(u->r, v);
            u->recalc();
            return u;
        } else {
            v->l = merge(u, v->l);
            v->recalc();
            return v;
        }
    }

    Node* insert(Node* u, Node* v, int k) {
        auto pa = split(u, k);
        return merge(merge(pa.first, v), pa.second);
    }

    //=======================

    int rnk(Node* u, int val) {
        if (!u) return 0;
        if (u->a < val)
            return cnt(u->l) + 1 + rnk(u->r, val);
        else
            return rnk(u->l, val);
    }

public:
    int rnk(int val) {
        return rnk(rt, val) + 1;
    }

    int kth(int k) {
        auto pa = split(rt, k);
        auto pb = split(pa.first, k - 1);
        int ret = pb.second->a;
        rt = merge(merge(pb.first, pb.second), pa.second);
        return ret;
    }

    void push(int val) {
        nodes[n] = Node(val);
        rt = n ? insert(rt, nodes + n, rnk(rt, val)) : nodes;
        n += 1;
    }

    void pop(int val) {
        auto pa = split(rt, rnk(rt, val));
        rt = merge(pa.first, split(pa.second, 1).second);
    }

    int pre(int val) {
        int k = rnk(rt, val);
        auto pa = split(rt, k);
        auto pb = split(pa.first, k - 1);
        int ret = pb.second->a;
        rt = merge(merge(pb.first, pb.second), pa.second);
        return ret;
    }

    int suc(int val) {
        int k = rnk(rt, val + 1);
        auto pa = split(rt, k);
        auto pb = split(pa.second, 1);
        int ret = pb.first->a;
        rt = merge(pa.first, merge(pb.first, pb.second));
        return ret;
    }
};

//=========================

int read() {
    int x = 0;
    char c = getchar();
    while ('0' > c || c > '9') c = getchar();
    while ('0' <= c && c <= '9') {
        x *= 10;
        x += c - '0';
        c = getchar();
    }
    return x;
}

char wBuf[10];
void write(int x) {
    if (!x) putchar('0');
    int cnt = 0;
    while (x) {
        wBuf[cnt++] = x % 10 + '0';
        x /= 10;
    }
    while (cnt) putchar(wBuf[--cnt]);
}

//=========================

int n, m, lst, ans;
Treap tr;

signed main() {
    n = read(), m = read();
    for (int i = 0; i < n; ++i) {
        int u = read();
        tr.push(u);
    }
    for (int i = 0; i < m; ++i) {
        int opt = read(), x = read() ^ lst;
        switch (opt) {
        case 1:
            tr.push(x);
            break;
        case 2:
            tr.pop(x);
            break;
        case 3:
            ans ^= lst = tr.rnk(x);
            break;
        case 4:
            ans ^= lst = tr.kth(x);
            break;
        case 5:
            ans ^= lst = tr.pre(x);
            break;
        case 6:
            ans ^= lst = tr.suc(x);
            break;
        }
    }
    write(ans);
    putchar('\n');

    return 0;
}

结果看,在偷了不少懒的情况下,平均比splay要稍快
而代码短了很多
其实可以发现,要实现这样的数据结构,关键在于两个基本操作以及 rnk
实际上 rnksplit 可以合并成按照权值进行分裂的版本(以下代码没测过)

//分裂出权值小于 k 的
pair<Node*, Node*> split(Node* u, int k) {
    if (!u) return {};
    if (u->a >= k) {
        auto pa = split(u->l, k);
        u->l = pa.second;
        u->recalc();
        return {pa.first, u};
    } else {
        auto pa = split(u->r, k - cnt(u->l) - 1);
        u->r = pa.first;
        u->recalc();
        return {u, pa.second};
    }
}

这么做的话可以减少一些常数,不过也有缺点

重要拓展

容易发现,数量版本的核心操作是不关心 val 的具体取值的,这一点与 Splay 非常相似
也就是说并不是实际上维护了BST本身,而是在满足 前者的权小于后者 的前提
更进一步地说,这是在维护这个序列本身,可以做一些区间操作
以区间翻转举例

区间翻转 flip
分成三步:提取区间,打标记,合并区间

auto pa = split(rt, r);
auto pb = split(pa.first, l - 1);
//提取的目标区间为 pb.second
pb.second->flip ^= 1;
//打上标记
rt = merge(merge(pb.first, pb.second), pa.second);
//合并区间

标记还需要具体实现,因此需要增加个pushFilp,有事没事下放一下

void Node::pushFlip() {
    if (flip) {
        flip = 0;
        if (l) l->flip ^= 1;
        if (r) r->flip ^= 1;
        swap(l, r);
    }
}

洛谷P3391 【模板】文艺平衡树

#include <iostream>
#include <random>
using namespace std;

const int MAX_N = 1E5 + 100;
mt19937 rnd(114514);

class Treap {
private:
    class Node {
    public:
        int a, k, c = 1;
        bool flip = 0;
        Node *l = 0, *r = 0;
        Node(int _a = 0) : a(_a), k(rnd()) {}
        void recalc() { c = cnt(l) + cnt(r) + 1; }
        void pushFlip() {
            if (flip) {
                flip = 0;
                if (l) l->flip ^= 1;
                if (r) r->flip ^= 1;
                swap(l, r);
            }
        }
    };

    int n;
    Node* rt;
    Node nodes[MAX_N];

    //==============

    static int cnt(Node* u) { return u ? u->c : 0; }

    pair<Node*, Node*> split(Node* u, int k) {
        if (!u) return {};
        u->pushFlip();
        if (cnt(u->l) >= k) {
            auto pa = split(u->l, k);
            u->l = pa.second;
            u->recalc();
            return {pa.first, u};
        } else {
            auto pa = split(u->r, k - cnt(u->l) - 1);
            u->r = pa.first;
            u->recalc();
            return {u, pa.second};
        }
    }

    Node* merge(Node* u, Node* v) {
        if (!u) return v;
        if (!v) return u;
        u->pushFlip();
        v->pushFlip();
        if (u->k < v->k) {
            v->l = merge(u, v->l);
            v->recalc();
            return v;
        } else {
            u->r = merge(u->r, v);
            u->recalc();
            return u;
        }
    }

    void print(Node* u) {
        if (!u) return;
        u->pushFlip();
        print(u->l);
        cout << u->a << ' ';
        print(u->r);
    }

public:
    void init(int _n) {
        n = _n;
        nodes[0] = Node(1);
        rt = nodes;
        for (int i = 1; i < n; ++i) {
            nodes[i] = Node(i + 1);
            rt = merge(rt, nodes + i);
        }
    }

    void flip(int l, int r) {
        auto pa = split(rt, r);
        auto pb = split(pa.first, l - 1);
        pb.second->flip ^= 1;
        rt = merge(merge(pb.first, pb.second), pa.second);
    }

    void print() {
        print(rt);
    }
};

int n, m;
Treap tr;

int main() {
    ios::sync_with_stdio(false);
    cin >> n >> m;
    tr.init(n);
    while (m--) {
        int l, r;
        cin >> l >> r;
        tr.flip(l, r);
    }
    tr.print();

    return 0;
}

效率差不多,和splay一比又短了

Link-Cut-Tree

既然这玩意能区间翻转了,那也就能做LCT了
(留个坑