平衡树练习总结
P6136 【模板】普通平衡树(数据加强版)
狠狠地被有旋 Treap 恶心了一把,从此再也不写有旋 Treap!
还是 FHQ Treap 爽,比有旋 Treap 短一半。
有旋 Treap
结构体及相关数据定义
const int INF=1e18;
struct Treap{
int ls,rs;
int val,dat;
int cnt,sz;
}tree[M+N];
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int root,idx;
建立新点,返回新点编号
mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
tree[++idx]={0,0,val,(int)engine(),1,1};
return idx;
}
更新节点 \(p\) 的大小
inline void update(int p)
{
tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+tree[p].cnt;
return;
}
右旋(zig
)左旋(zag
)。
注意函数和左右的对应关系!先右再左!(谁发明的反人类命名)
inline void zig(int &p) //right rotation
{
int q=ls(p);
tree[p].ls=rs(q);
tree[q].rs=p;
p=q;
update(rs(p)),update(p);
return;
}
inline void zag(int &p) //left rotation
{
int q=rs(p);
tree[p].rs=ls(q);
tree[q].ls=p;
p=q;
update(ls(p)),update(p);
return;
}
建树,添加 \(-\infty\) 和 \(+\infty\) 两个点,其中 \(-\infty\) 作为父节点,\(+\infty\) 作为右子节点。
据说直接建会导致两点的 \(dat\) 不一定满足堆的关系,所以标有 FLAG
的地方调整了一下 \(dat\) 的大小关系。不过没有这一行也能过,因为第一次向根右边加点的时候就会自动把关系调对。
其实这些没什么必要关注,因为 \(dat\) 只是为了维持 Treap 的平衡而存在的,有那么几个点乱了影响不大。
inline void Build()
{
root=create(-INF);
tree[root].rs=create(INF);
tree[rs(root)].dat=tree[root].dat-1; //FLAG
update(root);
return;
}
根据值获取排名,没什么需要注意的。
此处和下面标有 error
的行在合法输入下理论上不会进入。
int get_rank(int val,int p)
{
if(!p) return 1;
if(val==tree[p].val) return tree[ls(p)].sz+1;
if(val<tree[p].val) return get_rank(val,ls(p));
if(val>tree[p].val) return get_rank(val,rs(p))+tree[ls(p)].sz+tree[p].cnt;
return -INF; //error
}
根据排名获取值,同样没有什么需要注意的。
int get_value(int rank,int p)
{
if(!p) return INF; //error
if(rank<=tree[ls(p)].sz) return get_value(rank,ls(p));
else if(rank<=tree[ls(p)].sz+tree[p].cnt) return tree[p].val;
else return get_value(rank-tree[ls(p)].sz-tree[p].cnt,rs(p));
}
插入节点,需要注意旋转的方向,最好手动模拟一下。
右旋(sig
)是把左子节点拉上来,左旋(zag
)是把右子节点拉上来。
void Insert(int val,int &p)
{
if(!p) {p=create(val); return;}
if(val==tree[p].val)
{
tree[p].cnt++;
update(p);
return;
}
if(val<tree[p].val)
{
Insert(val,ls(p));
if(tree[p].dat<tree[ls(p)].dat) zig(p);
}
if(val>tree[p].val)
{
Insert(val,rs(p));
if(tree[p].dat<tree[rs(p)].dat) zag(p);
}
update(p);
return;
}
删点,原理是把当前节点转到叶子上去删,注意此时需要保证 \(dat\) 之间的关系仍然正确,即 \(dat\) 大的做新的父节点。
void Remove(int val,int &p)
{
if(!p) return;
if(val==tree[p].val)
{
if(tree[p].cnt>1)
{
tree[p].cnt--;
update(p);
return;
}
else if(ls(p)||rs(p))
{
if(!rs(p) || tree[ls(p)].dat>tree[rs(p)].dat)
zig(p), Remove(val,rs(p));
else zag(p), Remove(val,ls(p));
update(p);
}
else p=0;
return;
}
if(val<tree[p].val) Remove(val,ls(p));
if(val>tree[p].val) Remove(val,rs(p));
update(p);
return;
}
找前驱,重头戏来了,就是这个函数害我多调 \(\infty\) 小时。
在有标记的一行,我原本是这样写的:
if(val<tree[p].val) p=ls(p);
if(val>tree[p].val) p=rs(p);
一眼看过去,似乎没问题是吧。但是!第一行修改完后的新 \(p\) 在第二行可能再次判断成功,又被更新一次。所以我成功地 WA 了 \(30+\) 发。
改成这样:
if(val<tree[p].val) p=ls(p);
else p=rs(p);
或下面的三目运算符就没问题了。
int get_prev(int val)
{
int res=0,p=root;
while(p)
{
if(val==tree[p].val)
{
if(ls(p)>0)
{
p=ls(p);
while(rs(p)>0) p=rs(p);
res=p;
}
break;
}
if(tree[p].val<val && (tree[p].val>tree[res].val || !res)) res=p;
p=val<tree[p].val?ls(p):rs(p); //WTF!!!!!!!!!!!!!!!!!!
}
return tree[res].val;
}
找后继,这里的注意事项同上。
int get_next(int val)
{
int res=0,p=root;
while(p)
{
if(val==tree[p].val)
{
if(rs(p))
{
p=rs(p);
while(ls(p)) p=ls(p);
res=p;
}
break;
}
if(tree[p].val>val && (tree[p].val<tree[res].val || !res)) res=p;
p=val<tree[p].val?ls(p):rs(p);
}
return tree[res].val;
}
这告诉我们,在代码中需要时刻关注变量的变化,多个 if
或是带引用的函数等都会改变变量的值,这时候就要留心新值是不是我们所需要的。
另外还有,少用默认函数参数(int haha(int x=0)
这种),这样写一旦代码中不小心漏了参数,编译器不会报错,浪费许多时间差错。
完整代码:
点击查看代码
#include<cstdio>
#include<random>
#include<chrono>
#define int long long
using namespace std;
namespace IO{
template<typename TYPE> void read(TYPE &x)
{
x=0; bool neg=false; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^'0');ch=getchar();}
if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;
const int N=1e6+5,M=3e6+5;
int n,m,a[N];
namespace TREAP{
const int INF=1e18;
struct Treap{
int ls,rs;
int val,dat;
int cnt,sz;
}tree[M+N];
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int root,idx;
mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
tree[++idx]={0,0,val,(int)engine(),1,1};
return idx;
}
inline void update(int p)
{
tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+tree[p].cnt;
return;
}
inline void zig(int &p) //right rotation
{
int q=ls(p);
tree[p].ls=rs(q);
tree[q].rs=p;
p=q;
update(rs(p)),update(p);
return;
}
inline void zag(int &p) //left rotation
{
int q=rs(p);
tree[p].rs=ls(q);
tree[q].ls=p;
p=q;
update(ls(p)),update(p);
return;
}
inline void Build()
{
root=create(-INF);
tree[root].rs=create(INF);
tree[rs(root)].dat=tree[root].dat-1;
update(root);
return;
}
int get_rank(int val,int p)
{
if(!p) return 1;
if(val==tree[p].val) return tree[ls(p)].sz+1;
if(val<tree[p].val) return get_rank(val,ls(p));
if(val>tree[p].val) return get_rank(val,rs(p))+tree[ls(p)].sz+tree[p].cnt;
return -INF; //error
}
int get_value(int rank,int p)
{
if(!p) return INF; //error
if(rank<=tree[ls(p)].sz) return get_value(rank,ls(p));
else if(rank<=tree[ls(p)].sz+tree[p].cnt) return tree[p].val;
else return get_value(rank-tree[ls(p)].sz-tree[p].cnt,rs(p));
}
void Insert(int val,int &p)
{
if(!p) {p=create(val); return;}
if(val==tree[p].val)
{
tree[p].cnt++;
update(p);
return;
}
if(val<tree[p].val)
{
Insert(val,ls(p));
if(tree[p].dat<tree[ls(p)].dat) zig(p);
}
if(val>tree[p].val)
{
Insert(val,rs(p));
if(tree[p].dat<tree[rs(p)].dat) zag(p);
}
update(p);
return;
}
void Remove(int val,int &p)
{
if(!p) return;
if(val==tree[p].val)
{
if(tree[p].cnt>1)
{
tree[p].cnt--;
update(p);
return;
}
else if(ls(p)||rs(p))
{
if(!rs(p) || tree[ls(p)].dat>tree[rs(p)].dat)
zig(p), Remove(val,rs(p));
else zag(p), Remove(val,ls(p));
update(p);
}
else p=0;
return;
}
if(val<tree[p].val) Remove(val,ls(p));
if(val>tree[p].val) Remove(val,rs(p));
update(p);
return;
}
int get_prev(int val)
{
int res=0,p=root;
while(p)
{
if(val==tree[p].val)
{
if(ls(p)>0)
{
p=ls(p);
while(rs(p)>0) p=rs(p);
res=p;
}
break;
}
if(tree[p].val<val && (tree[p].val>tree[res].val || !res)) res=p;
p=val<tree[p].val?ls(p):rs(p); //WTF!!!!!!!!!!!!!!!!!!
}
return tree[res].val;
}
int get_next(int val)
{
int res=0,p=root;
while(p)
{
if(val==tree[p].val)
{
if(rs(p))
{
p=rs(p);
while(ls(p)) p=ls(p);
res=p;
}
break;
}
if(tree[p].val>val && (tree[p].val<tree[res].val || !res)) res=p;
p=val<tree[p].val?ls(p):rs(p);
}
return tree[res].val;
}
}
signed main()
{
read(n),read(m);
TREAP::Build();
for(int i=1;i<=n;i++)
{
read(a[i]);
TREAP::Insert(a[i],TREAP::root);
}
int ans=0,last=0;
int tot=n;
for(int i=1;i<=m;i++)
{
int op,x; read(op),read(x);
x^=last;
switch(op)
{
case 1:
TREAP::Insert(x,TREAP::root);
tot++;
break;
case 2:
TREAP::Remove(x,TREAP::root);
tot--;
break;
case 3:
last=TREAP::get_rank(x,TREAP::root)-1;
ans^=last;
break;
case 4:
last=TREAP::get_value(x+1,TREAP::root);
ans^=last;
break;
case 5:
last=TREAP::get_prev(x);
ans^=last;
break;
case 6:
last=TREAP::get_next(x);
ans^=last;
break;
default:
break;
}
}
write(ans,'\n');
return 0;
}
无旋 Treap(FHQ Treap)
相关变量及结构体定义。
struct Treap{
int ls,rs;
int val,dat;
int sz;
}tree[N+M];
int idx,root;
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
新建点,注意 FHQ Treap 不会记录某个值出现的次数,而是每一个都新建一个点。
mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
tree[++idx]={0,0,val,(int)engine(),1};
return idx;
}
更新点 \(p\) 子树大小。
inline void update(int p)
{
tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+1;
return;
}
分割函数。
引用的 \(L\)、\(R\) 相当于是预留的一个位置,要求处理过后把两棵子树的根放在这个空位里面。
void Split(int val,int p,int &L,int &R)
{
if(!p) {L=R=0; return;}
if(tree[p].val<=val)
{
L=p;
Split(val,rs(p),rs(p),R);
update(L);
}
else
{
R=p;
Split(val,ls(p),L,ls(p));
update(R);
}
return;
}
合并两棵树,返回根节点。
注意此时要求 \(dat\) 满足堆性质,所以 需要把 \(dat\) 较大的作为根节点。
int Merge(int L,int R)
{
if(!L||!R) return L|R;
if(tree[L].dat>tree[R].dat)
{
rs(L)=Merge(rs(L),R);
update(L);
return L;
}
else
{
ls(R)=Merge(L,ls(R));
update(R);
return R;
}
}
插入节点,其实就是找到位置以后放进去。
void Insert(int val)
{
int p=create(val);
int L,R; Split(val,root,L,R);
root=Merge(L,Merge(p,R));
return;
}
删除节点。
直接分割出来的是一棵子树,里面全部都是所求值。因为只用删除一个,所以只删根节点,其它的全部合并起来就可以了。
void Remove(int val)
{
int NR,R; Split(val,root,NR,R);
int L,MID; Split(val-1,NR,L,MID);
MID=Merge(ls(MID),rs(MID));
root=Merge(L,Merge(MID,R));
return;
}
根据值获取排名。这里获取到的一定是第一个该值的排名。
int get_rank(int val)
{
int L,R; Split(val-1,root,L,R);
int res=tree[L].sz+1;
root=Merge(L,R);
return res;
}
根据排名获取值,这次不能用分裂合并来取巧了。
int get_value(int rank)
{
int p=root;
while(p)
{
int lsz=tree[ls(p)].sz;
if(rank==lsz+1) return tree[p].val;
if(rank<=lsz) p=ls(p);
else rank-=lsz+1,p=rs(p);
}
return -1;
}
逆天超短代码。
找前驱直接找排名比它第一位的即可(找某个值的排名一定是第一个该值的排名,所以减一以后就是前驱)。
int get_prev(int val)
{
return get_value(get_rank(val)-1);
}
找后继不能再用排名,但是由于 get_rank
的特性,其结果更像是 lower_bound
,所以找所求值加一对应排名对应值即可。
int get_next(int val)
{
return get_value(get_rank(val+1));
}
完整代码(就喜欢短的)
点击查看代码
#include<cstdio>
#include<random>
#include<chrono>
using namespace std;
namespace IO{
template<typename TYPE> void read(TYPE &x)
{
x=0; bool neg=false; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^'0');ch=getchar();}
if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;
const int N=1e5+5,M=1e6+5;
int n,m,a[N];
namespace FHQ{
struct Treap{
int ls,rs;
int val,dat;
int sz;
}tree[N+M];
int idx,root;
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
tree[++idx]={0,0,val,(int)engine(),1};
return idx;
}
inline void update(int p)
{
tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+1;
return;
}
void Split(int val,int p,int &L,int &R) //L,R¿ÉÀí½âΪÏóÕ÷Ò»¸öλÖÃ
{
if(!p) {L=R=0; return;}
if(tree[p].val<=val)
{
L=p;
Split(val,rs(p),rs(p),R);
update(L);
}
else
{
R=p;
Split(val,ls(p),L,ls(p));
update(R);
}
return;
}
int Merge(int L,int R)
{
if(!L||!R) return L|R;
if(tree[L].dat>tree[R].dat)
{
rs(L)=Merge(rs(L),R);
update(L);
return L;
}
else
{
ls(R)=Merge(L,ls(R));
update(R);
return R;
}
return 0;
}
void Insert(int val)
{
int p=create(val);
int L,R; Split(val,root,L,R);
root=Merge(L,Merge(p,R));
return;
}
void Remove(int val)
{
int NR,R; Split(val,root,NR,R);
int L,MID; Split(val-1,NR,L,MID);
MID=Merge(ls(MID),rs(MID));
root=Merge(L,Merge(MID,R));
return;
}
int get_rank(int val)
{
int L,R; Split(val-1,root,L,R);
int res=tree[L].sz+1;
root=Merge(L,R);
return res;
}
int get_value(int rank)
{
int p=root;
while(p)
{
int lsz=tree[ls(p)].sz;
if(rank==lsz+1) return tree[p].val;
if(rank<=lsz) p=ls(p);
else rank-=lsz+1,p=rs(p);
}
return -1;
}
int get_prev(int val)
{
return get_value(get_rank(val)-1);
}
int get_next(int val)
{
return get_value(get_rank(val+1));
}
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
{
read(a[i]);
FHQ::Insert(a[i]);
}
int last=0,ans=0;
for(int i=1;i<=m;i++)
{
int op,x; read(op),read(x);
x^=last;
switch(op)
{
case 1:
FHQ::Insert(x);
break;
case 2:
FHQ::Remove(x);
break;
case 3:
last=FHQ::get_rank(x);
ans^=last;
break;
case 4:
last=FHQ::get_value(x);
ans^=last;
break;
case 5:
last=FHQ::get_prev(x);
ans^=last;
break;
case 6:
last=FHQ::get_next(x);
ans^=last;
break;
}
}
write(ans,'\n');
return 0;
}
P3391 【模板】文艺平衡树
这道题只用了 FHQ Treap,真的爽。
此题中 \(val\) 值不再是静态的,因为有交换子树的操作,也不可能是静态的。
而这个动态的 \(val\) 就是数列的下标,下标完美满足 BST 的所有条件:某个数左边数的下标都比它小,而右边都比它大。
而这个下标既然是动态的,自然不能存起来,在分割函数内动态地就可以获取当前节点的下标。
这种方法理论上扩展性很强,区间问题都可以这么转换。
完整代码:
点击查看代码
#include<cstdio>
#include<random>
#include<chrono>
using namespace std;
namespace IO{
template<typename TYPE> void read(TYPE &x)
{
x=0; bool neg=false; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^'0');ch=getchar();}
if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;
const int N=1e5+5,M=1e5+5;
int n,m;
namespace FHQ{
struct Treap{
int ls,rs;
int id,dat;
bool lazy;
int sz;
}tree[M];
int idx,root;
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int id)
{
tree[++idx]={0,0,id,(int)engine(),false,1};
return idx;
}
inline void update(int p)
{
tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+1;
return;
}
void spread(int p)
{
if(tree[p].lazy)
{
tree[ls(p)].lazy^=true;
tree[rs(p)].lazy^=true;
swap(ls(p),rs(p));
tree[p].lazy=false;
}
return;
}
void Split(int pos,int p,int &L,int &R)
{
if(!p){L=R=0; return;}
spread(p);
if(tree[ls(p)].sz+1<=pos)
{
L=p;
Split(pos-(tree[ls(p)].sz+1),rs(p),rs(p),R);
update(L);
}
else
{
R=p;
Split(pos,ls(p),L,ls(p));
update(R);
}
return;
}
int Merge(int L,int R)
{
if(!L||!R) return L|R;
if(tree[L].dat>tree[R].dat)
{
spread(L);
rs(L)=Merge(rs(L),R);
update(L);
return L;
}
else
{
spread(R);
ls(R)=Merge(L,ls(R));
update(R);
return R;
}
}
void Reverse(int l,int r)
{
int NR,R; Split(r,root,NR,R);
int L,MID; Split(l-1,NR,L,MID);
tree[MID].lazy^=true;
root=Merge(Merge(L,MID),R);
return;
}
void DFS(int p)
{
if(!p) return;
spread(p);
DFS(ls(p));
write(tree[p].id,' ');
DFS(rs(p));
return;
}
} using namespace FHQ;
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++)
root=Merge(root,create(i));
for(int i=1;i<=m;i++)
{
int l,r; read(l),read(r);
Reverse(l,r);
}
DFS(root);
return 0;
}
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18546756