替罪羊树 ← 洛谷P3369、AcWing253

【题目来源】
https://www.luogu.com.cn/problem/P3369

https://www.acwing.com/problem/content/255/

【题目描述】
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1.插入 x 数
2.删除 x 数(若有多个相同的数,因只删除一个)
3.查询 x 数的排名(排名定义为比当前数小的数的个数 +1)
4.查询排名为 x 的数
5.求 x 的前驱(前驱定义为小于 x,且最大的数)
6.求 x 的后继(后继定义为大于 x,且最小的数)

【输入格式】
第一行为 n,表示操作的个数。
下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)。

【输出格式】
对于操作 3,4,5,6,每行输出一个数,表示对应答案。

【数据范围】
对于100%的数据,1≤n≤10^5,∣x∣≤10^7。

【算法分析】

替罪羊树(Scapegoat Tree),作为最简单的一种维护二叉树平衡的BST,采用简单而暴力的“摧毁、重建”方法维护二叉树的平衡:
1.若二叉树的某棵子树不平衡了,先求得这棵子树的
中序遍历序列,然后再删除这棵子树。谓之“摧毁”。→ 形容为“拍平”。
2.以第1步得到的中序遍历序列的
中间元素为根,构建一棵平衡的子树。谓之“重建”。→ 形容为“拎起来”。

算法竞赛中常用的 BST,学习
难度从小到大的是替罪羊树、笛卡尔树、Treap树、FHQ Treap树、K-D树、Splay树、LCT。

替罪羊树常用于维护K-D树的平衡。

若每插入或删除一个结点,就立刻“摧毁、重建”整棵二叉树,
代价太大。因此,在代码实践中,通常维护一棵“差不多平衡”的二叉树即可。为了评估这个“差不多平衡”,引入“不平衡率α”。

设二叉树某个结点 x 的子树总结点数为 n ,左右子树的结点数分别为 le,ri ,若 max(le,ri)/n>α ,即
max(le,ri)/(le+ri)>α,我们就“摧毁、重建”当前子树。否则,认为这棵子树是平衡的,不进行任何操作。
通常选择不平衡率α的值为 
0.7,这是因为经验表明,当选用不平衡率α的值为 0.7时,二叉树的重建次数较少,二叉树的深度也较小。

【算法代码】

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;
const double alpha=0.7;

struct node {
    int le,ri;
    int cnt,val,size;
} sgt[maxn];

int n,m;
int cnt=1;

/*
tp 表示子树中结点下标组成的线性序列
tn 表示子树中结点个数组成的线性序列
tv 表示子树中结点权值组成的线性序列
*/
vector<int> tp,tn,tv;

bool exist(int x) {
    return !(sgt[x].le==0 && sgt[x].ri==0 && sgt[x].cnt==0);
}

int flatten(int x) {
    if(exist(sgt[x].le)) flatten(sgt[x].le);
    int id=tp.size();
    if(sgt[x].cnt) {
        tp.push_back(x);
        tn.push_back(sgt[x].cnt);
        tv.push_back(sgt[x].val);
    }
    if(exist(sgt[x].ri)) flatten(sgt[x].ri);
    return id;
}

void rebuild(int x,int le,int ri) {
    int mid=(le+ri)/2;
    int sizel=0,sizer=0;
    if(le<mid) {
        sgt[x].le=tp[(le+mid-1)/2];
        rebuild(sgt[x].le,le,mid-1);
        sizel=sgt[sgt[x].le].size;
    } else sgt[x].le=0;
    if(ri>mid) {
        sgt[x].ri=tp[(mid+1+ri)/2];
        rebuild(sgt[x].ri,mid+1,ri);
        sizer=sgt[sgt[x].ri].size;
    } else sgt[x].ri=0;
    sgt[x].cnt=tn[mid];
    sgt[x].val=tv[mid];
    sgt[x].size=sizel+sizer+sgt[x].cnt;
}

void restr(int x) {
    double val=max(sgt[sgt[x].le].size, sgt[sgt[x].ri].size)*1.0/sgt[x].size;
    if(val>alpha) {
        tp.clear();
        tn.clear();
        tv.clear();
        int id=flatten(x);
        swap(tp[id],tp[(tp.size()-1)/2]);
        rebuild(x,0,tp.size()-1);
    }
}

void insert(int x, int val) {
    if(!exist(x)) {
        sgt[x].cnt=1;
        sgt[x].val=val;
    } else if(val<sgt[x].val) {
        if(!exist(sgt[x].le)) sgt[x].le=++cnt;
        insert(sgt[x].le,val);
    } else if(val>sgt[x].val) {
        if (!exist(sgt[x].ri)) sgt[x].ri=++cnt;
        insert(sgt[x].ri,val);
    } else sgt[x].cnt++;
    sgt[x].size++;
    restr(x);
}

void del(int x, int val) {
    sgt[x].size--;
    if(val<sgt[x].val) del(sgt[x].le,val);
    else if (val>sgt[x].val) del(sgt[x].ri,val);
    else sgt[x].cnt--;
    restr(x);
}

int ord_pre(int x, int val) {
    if(val<sgt[x].val)
        return (exist(sgt[x].le)?ord_pre(sgt[x].le,val):0);
    else if(val>sgt[x].val)
        return sgt[sgt[x].le].size+sgt[x].cnt+(exist(sgt[x].ri)?ord_pre(sgt[x].ri,val):0);
    else
        return sgt[sgt[x].le].size;
}

int ord_nxt(int x, int val) {
    if(val>sgt[x].val)
        return ((exist(sgt[x].ri)?ord_nxt(sgt[x].ri,val):0));
    else if(val<sgt[x].val)
        return sgt[sgt[x].ri].size+sgt[x].cnt+(exist(sgt[x].le)?ord_nxt(sgt[x].le,val):0);
    else
        return sgt[sgt[x].ri].size;
}

int ord(int val) {
    return ord_pre(1,val)+1;
}

int find(int x, int rk) {
    if(rk<=sgt[sgt[x].le].size)
        return find(sgt[x].le, rk);
    else if(rk>sgt[sgt[x].le].size+sgt[x].cnt)
        return find(sgt[x].ri, rk-sgt[sgt[x].le].size-sgt[x].cnt);
    else
        return sgt[x].val;
}

int pre(int x) {
    int rk=ord_pre(1,x);
    return find(1,rk);
}

int nxt(int x) {
    int rk=ord_nxt(1,x);
    return find(1,sgt[1].size-rk+1);
}

int main() {
    int opt,x;
    scanf("%d",&n);
    for(int i=1; i<=n; i++) {
        scanf("%d%d",&opt,&x);
        if(opt==1) insert(1,x);
        else if(opt==2) del(1,x);
        else if(opt==3) printf("%d\n",ord(x));
        else if(opt==4) printf("%d\n",find(1,x));
        else if(opt==5) printf("%d\n",pre(x));
        else printf("%d\n",nxt(x));
    }
    return 0;
}


/*
in:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

out:
106465
84185
492737
*/




【参考文献】
https://oi-wiki.org/ds/sgt/
https://www.cnblogs.com/lingspace/p/scapegoat-tree.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值