关于 Treap 维护堆的性质 的一些问题
  • 板块学术版
  • 楼主Conan15
  • 当前回复0
  • 已保存回复0
  • 发布时间2024/6/9 11:22
  • 上次更新2024/6/9 15:14:42
查看原帖
关于 Treap 维护堆的性质 的一些问题
565040
Conan15楼主2024/6/9 11:22

昨天我重新顺了一遍 Treap 的思路,写了一篇博客(尚未发布),然后打算把板子敲一遍顺便放上去,结果……

之前写的板子是大根堆的:

#include <bits/stdc++.h>
using namespace std;
const int N = 100010, INF = 1e8;
int n;
struct Node {
    int l, r;
    int key, val;
    int cnt, size;
} tr[N];
int root, idx;

void push_up(int p) {  //把信息往上传递
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
    //易错点:后面加上cnt不是1
}

int get_node(int key) {
    tr[++idx].key = key;
    tr[idx].val = rand();
    tr[idx].cnt = tr[idx].size = 1;
    return idx;
}

void zag(int &p) {     //左旋
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;
    push_up(tr[p].l), push_up(p);
}

void zig(int &p) {     //右旋
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    push_up(tr[p].r), push_up(p);
}

void build() {
    get_node(-INF); get_node(INF);  //初始化先插入两个数
    //分别为正无穷和负无穷
    root = 1; tr[1].r = 2;  
    push_up(root);
    if (tr[1].val < tr[2].val) zag(root);
}

void insert(int &p, int key) {
    if (!p) p = get_node(key);
    else if (tr[p].key == key) tr[p].cnt++;
    else if (tr[p].key > key) {    //往左子树
        insert(tr[p].l, key);
        if (tr[tr[p].l].val > tr[p].val) zig(p);  //右旋调整
    } else {                     //往右子树
        insert(tr[p].r, key);
        if (tr[tr[p].r].val > tr[p].val) zag(p);  //左旋调整
    }
    push_up(p);
}

void remove(int &p, int key) {
    if (!p) return;
    if (tr[p].key == key) {
        if (tr[p].cnt > 1) tr[p].cnt--;   //如果cnt大于一个则直接减去一个
        else if (tr[p].l || tr[p].r) {    //不是叶子节点
            if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val) {
                //右子树是空的或左子树的val大于右子树
                zig(p);       //右旋
                remove(tr[p].r, key);  //再删除右子树
            } else {
                zag(p);       //左旋
                remove(tr[p].l, key);  //在删除左子树
            }
        } else p = 0;                     //是叶子节点则直接删除
    }
    else if (tr[p].key > key) remove(tr[p].l, key);  //在左边删
    else remove(tr[p].r, key);                       //在右边删
    push_up(p);
}

int get_rank_by_key(int p, int key) {       //通过数值找排名
    if (!p) return 0;     //在题目中不会出现的情况,但也可以特判
    if (tr[p].key == key) return tr[tr[p].l].size + 1;
    if (tr[p].key > key) return get_rank_by_key(tr[p].l, key);  //往左子树找
    return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}

int get_key_by_rank(int p, int rank) {      //通过排名找数值
    if (!p) return INF;   //不会出现的情况
    if (tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l, rank);  //一定在左边
    if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
    return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

int get_prev(int p, int key) {             //查找严格小于key的最大数
    if (!p) return -INF;
    if (tr[p].key >= key) return get_prev(tr[p].l, key);
    return max(tr[p].key, get_prev(tr[p].r, key));
}

int get_next(int p, int key) {             //查找严格大于key的最小数
    if (!p) return INF;
    if (tr[p].key <= key) return get_next(tr[p].r, key);
    return min(tr[p].key, get_next(tr[p].l, key));
}

int main() {
    build();
    scanf("%d", &n);
    while (n--) {
        int opt, x;
        scanf("%d%d", &opt, &x);
        if (opt == 1) insert(root, x);                                 //插入一个数
        else if (opt == 2) remove(root, x);                            //删除一个数
        //由于有两个哨兵,因此排名会有1的误差。
        else if (opt == 3) printf("%d\n", get_rank_by_key(root, x) - 1);   //查询x的排名
        else if (opt == 4) printf("%d\n", get_key_by_rank(root, x + 1));   //查询排名为x的数
        else if (opt == 5) printf("%d\n", get_prev(root, x));          //查询x的前驱
        else printf("%d\n", get_next(root, x));          //查询x的后继
    }
    return 0;
}

然后我自己写了一遍小根堆的,发现不太对,然后我就把上面这份代码也改成小根堆的:

#include <bits/stdc++.h>
using namespace std;
const int N = 100010, INF = 1e8;
int n;
struct Node {
    int l, r;
    int key, val;
    int cnt, size;
} tr[N];
int root, idx;

void push_up(int p) {  //把信息往上传递
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
    //易错点:后面加上cnt不是1
}

int get_node(int key) {
    tr[++idx].key = key;
    tr[idx].val = rand();
    tr[idx].cnt = tr[idx].size = 1;
    return idx;
}

void zag(int &p) {     //左旋
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;
    push_up(tr[p].l), push_up(p);
}

void zig(int &p) {     //右旋
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    push_up(tr[p].r), push_up(p);
}

void build() {
    get_node(-INF); get_node(INF);  //初始化先插入两个数
    //分别为正无穷和负无穷
    root = 1; tr[1].r = 2;  
    push_up(root);
    if (tr[1].val > tr[2].val) zag(root);
}

void insert(int &p, int key) {
    if (!p) p = get_node(key);
    else if (tr[p].key == key) tr[p].cnt++;
    else if (tr[p].key > key) {    //往左子树
        insert(tr[p].l, key);
        if (tr[tr[p].l].val < tr[p].val) zig(p);  //右旋调整
    } else {                     //往右子树
        insert(tr[p].r, key);
        if (tr[tr[p].r].val < tr[p].val) zag(p);  //左旋调整
    }
    push_up(p);
}

void remove(int &p, int key) {
    if (!p) return;
    if (tr[p].key == key) {
        if (tr[p].cnt > 1) tr[p].cnt--;   //如果cnt大于一个则直接减去一个
        else if (tr[p].l || tr[p].r) {    //不是叶子节点
            if (!tr[p].r || tr[tr[p].l].val < tr[tr[p].r].val) {
                //右子树是空的或左子树的val大于右子树
                zig(p);       //右旋
                remove(tr[p].r, key);  //再删除右子树
            } else {
                zag(p);       //左旋
                remove(tr[p].l, key);  //在删除左子树
            }
        } else p = 0;                     //是叶子节点则直接删除
    }
    else if (tr[p].key > key) remove(tr[p].l, key);  //在左边删
    else remove(tr[p].r, key);                       //在右边删
    push_up(p);
}

int get_rank_by_key(int p, int key) {       //通过数值找排名
    if (!p) return 0;     //在题目中不会出现的情况,但也可以特判
    if (tr[p].key == key) return tr[tr[p].l].size + 1;
    if (tr[p].key > key) return get_rank_by_key(tr[p].l, key);  //往左子树找
    return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}

int get_key_by_rank(int p, int rank) {      //通过排名找数值
    if (!p) return INF;   //不会出现的情况
    if (tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l, rank);  //一定在左边
    if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
    return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

int get_prev(int p, int key) {             //查找严格小于key的最大数
    if (!p) return -INF;
    if (tr[p].key >= key) return get_prev(tr[p].l, key);
    return max(tr[p].key, get_prev(tr[p].r, key));
}

int get_next(int p, int key) {             //查找严格大于key的最小数
    if (!p) return INF;
    if (tr[p].key <= key) return get_next(tr[p].r, key);
    return min(tr[p].key, get_next(tr[p].l, key));
}

int main() {
    build();
    scanf("%d", &n);
    while (n--) {
        int opt, x;
        scanf("%d%d", &opt, &x);
        if (opt == 1) insert(root, x);                                 //插入一个数
        else if (opt == 2) remove(root, x);                            //删除一个数
        //由于有两个哨兵,因此排名会有1的误差。
        else if (opt == 3) printf("%d\n", get_rank_by_key(root, x) - 1);   //查询x的排名
        else if (opt == 4) printf("%d\n", get_key_by_rank(root, x + 1));   //查询排名为x的数
        else if (opt == 5) printf("%d\n", get_prev(root, x));          //查询x的前驱
        else printf("%d\n", get_next(root, x));          //查询x的后继
    }
    return 0;
}

结果小根堆的代码错了 7 个点,共计 11 个点。

求教为什么它错了(



另外,我自己昨天写的代码(小根堆),把 remove 函数判定是否旋转的那一行改一下符号就好了,这难道不玄学吗(

代码如下(由于是重新写的,部分变量和上面不太一样)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 15, INF = 0x3f3f3f3f;

int n;
struct Treap {
    int ls, rs;
    int val, prio;  //权值、随机赋值
    int cnt, sz;    //出现次数、子树大小
} tr[N];

int rt, idx = 0;

void pushup(int u) {    //统计以 u 为根子树的信息
    tr[u].sz = tr[tr[u].ls].sz + tr[tr[u].rs].sz + tr[u].cnt;   //一定不要忘记加上它自己的 cnt!
    //是加 cnt,不是加 1!因为一个元素不只出现一次!!
}

int get_node(int val) {   //新建一个节点,元素为 val,返回其编号
    tr[++idx].val = val;
    tr[idx].prio = rand();
    tr[idx].cnt = tr[idx].sz = 1;
    return idx;
}

void zag(int &u) {    //左旋
    int v = tr[u].rs;   //u 的右儿子
    tr[u].rs = tr[v].ls, tr[v].ls = u, u = v;
    pushup(tr[u].ls), pushup(u);
}

void zig(int &u) {
    int v = tr[u].ls;
    tr[u].ls = tr[v].rs, tr[v].rs = u, u = v;
    pushup(tr[u].rs), pushup(u);
}

void init() {
    get_node(-INF), get_node(INF);  //设置两个哨兵,减少查询的特判
    rt = 1; tr[1].rs = 2;   //设置两个新节点之间的关系
    if (tr[1].prio > tr[2].prio) zag(rt);   //显然要维护堆的性质
}

void insert(int &u, int val) {
    if (!u) u = get_node(val);
    else if (val == tr[u].val) tr[u].cnt++;
    else if (val < tr[u].val) {
        insert(tr[u].ls, val);
        if (tr[tr[u].ls].prio < tr[u].prio) zig(u);
    } else if (val > tr[u].val) {
        insert(tr[u].rs, val);
        if (tr[tr[u].rs].prio < tr[u].prio) zag(u);
    }
    pushup(u);
}

void remove(int &u, int val) {
    if (!u) return;
    if (val == tr[u].val) {
        if (tr[u].cnt > 1) tr[u].cnt--;
        else if (!tr[u].ls && !tr[u].rs) u = 0;
        else {
            if (!tr[u].rs || tr[tr[u].ls].prio > tr[tr[u].rs].prio) {
                zig(u);
                remove(tr[u].rs, val);
            } else {
                zag(u);
                remove(tr[u].ls, val);
            }
        }
    } else {
        if (val < tr[u].val) remove(tr[u].ls, val);
        else remove(tr[u].rs, val);
    }
    pushup(u);
}

int get_val_by_rank(int u, int rank) {  //通过排名找元素
// cout << '\t' << u << ' ' << tr[u].ls << ' ' << tr[u].rs << endl;
    if (!u) return INF;
    if (tr[tr[u].ls].sz >= rank) return get_val_by_rank(tr[u].ls, rank);
    else if (tr[tr[u].ls].sz + tr[u].cnt < rank) return get_val_by_rank(tr[u].rs, rank - tr[tr[u].ls].sz - tr[u].cnt);
    else return tr[u].val;
}

int get_rank_by_val(int u, int val) {   //通过元素找排名
    if (!u) return 0;
    if (val == tr[u].val) return tr[tr[u].ls].sz + 1;
    if (val < tr[u].val) return get_rank_by_val(tr[u].ls, val);
    return tr[tr[u].ls].sz + tr[u].cnt + get_rank_by_val(tr[u].rs, val);
}

int get_pre(int u, int val) {   //前驱
    if (!u) return -INF;
    if (val <= tr[u].val) return get_pre(tr[u].ls, val);
    return max(tr[u].val, get_pre(tr[u].rs, val));
}

int get_nxt(int u, int val) {   //后继
    if (!u) return INF;
    if (val >= tr[u].val) return get_nxt(tr[u].rs, val);
    return min(tr[u].val, get_nxt(tr[u].ls, val));
}

int main() {
    srand(time(0));
    scanf("%d", &n);
    init();
    while (n--) {
        int opt, x; scanf("%d%d", &opt, &x);
        if (opt == 1) {
            insert(rt, x);
        } else if (opt == 2) {
            remove(rt, x);
        } else if (opt == 3) {
            printf("%d\n", get_rank_by_val(rt, x) - 1); //由于有哨兵,所以会有1的误差
        } else if (opt == 4) {
            printf("%d\n", get_val_by_rank(rt, x + 1));
        } else if (opt == 5) {
            printf("%d\n", get_pre(rt, x));
        } else {
            printf("%d\n", get_nxt(rt, x));
        }
    }
    return 0;
}
2024/6/9 11:22
加载中...