替罪羊树学习笔记
做法
简单来说,这东西就是一个平衡树,大多数时候平衡树需要旋转,但是替罪羊树不需要
它是通过不断重构维持平衡,就是当左右子树的节点数差异过大的时候,拍平重新建
所以这里涉及一个判断操作,就是什么情况下重构,和 K-D tree 一样,如果太大,就会失衡,太小,太频繁,不优
这样的话,插入和普通平衡树一样,但是插入完成后要判断是否符合要求,不符合重构
至于拍平,暴力 dfs 即可
对于删除,这里不能暴力删,而是先标记,在重构的时候删掉
例题:
普通平衡树
其实是四个操作,加点,删点,查排名,查值
因为写法区别,如果不将相同的数字合并,删除时应按排名删除
#include<cstdio>
using namespace std;
const double alpha=0.7;
int q,tot,cnt,rt;
struct tree{
int ls,rs,siz,val,v;
}tr[200005];
int id[200005];
void pushup(int p)
{
tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz+tr[p].v;
}
void build(int &p,int l,int r)
{
p=0;
if(l>r) return;
int mid=(l+r)>>1;
p=id[mid],tr[p].siz=tr[p].v=1;
build(tr[p].ls,l,mid-1);
build(tr[p].rs,mid+1,r);
pushup(p);
}
void dfs(int p)
{
if(!p) return;
dfs(tr[p].ls);
if(tr[p].v) id[++cnt]=p;
dfs(tr[p].rs);
}
void rebuild(int &p)
{
cnt=0;
dfs(p);
build(p,1,cnt);
}
void insert(int &p,int k)
{
if(!p)
{
p=++tot;
tr[p].siz=tr[p].v=1;
tr[p].val=k;
return;
}
if(k<tr[p].val)
{
insert(tr[p].ls,k);
}
else
{
insert(tr[p].rs,k);
}
if(tr[p].siz*alpha<=tr[tr[p].ls].siz||tr[p].siz*alpha<=tr[tr[p].rs].siz) rebuild(p);
pushup(p);
}
void del(int &p,int k)
{
if(tr[p].v&&k==tr[tr[p].ls].siz+1)
{
tr[p].siz--,tr[p].v=0;
return;
}
if(k<=tr[tr[p].ls].siz)
{
del(tr[p].ls,k);
}
else
{
del(tr[p].rs,k-tr[tr[p].ls].siz-tr[p].v);
}
if(tr[p].siz*alpha<=tr[tr[p].ls].siz||tr[p].siz*alpha<=tr[tr[p].rs].siz) rebuild(p);
pushup(p);
}
int getrnk(int p,int x)
{
if(!p) return 0;
if(tr[p].val<x)
{
return tr[tr[p].ls].siz+tr[p].v+getrnk(tr[p].rs,x);
}
else
{
return getrnk(tr[p].ls,x);
}
}
int getval(int p,int k)
{
if(tr[p].v&&tr[tr[p].ls].siz+1==k) return tr[p].val;
if(tr[tr[p].ls].siz<k) return getval(tr[p].rs,k-tr[tr[p].ls].siz-tr[p].v);
else return getval(tr[p].ls,k);
}
int main()
{
// freopen("1.txt","r",stdin);
scanf("%d",&q);
while(q--)
{
int opt,x;
scanf("%d%d",&opt,&x);
if(opt==1)
{
insert(rt,x);
}
if(opt==2)
{
del(rt,getrnk(rt,x)+1);
}
if(opt==3)
{
printf("%d\n",getrnk(rt,x)+1);
}
if(opt==4)
{
printf("%d\n",getval(rt,x));
}
if(opt==5)
{
printf("%d\n",getval(rt,getrnk(rt,x)));
}
if(opt==6)
{
printf("%d\n",getval(rt,getrnk(rt,x+1)+1));
}
}
return 0;
}
没有人的算术
https://www.gxyzoj.com/d/gxyznoi/p/P169
如果将所有的数都编号,那么就可以直接在线段树上更新或判断了
因为要不断的插入并维护大小关系,考虑平衡树
可以记录上下界 l,r,每次根的值为 \(mid=\frac{l+r}{2}\),左子树上界和右子树下界为 mid
但是如果进行旋转,所有值都要变,所以可以使用替罪羊树
因为是合并,所以左右的数都可以用替罪羊树的一个节点表示,而所有节点除 \((0,0)\) 外都能用两个存在且排好序的节点表示
所以就可以依次比较插入

浙公网安备 33010602011771号