平衡树
二叉搜索树
对于二叉搜索树上的任意一个节点 \(u\),其左子树任意一点的权值都小于它,右子树的任意一点的权值都大于它。
平衡
对于任意一节点 \(i\) 其左右节点的深度的差不超过 \(1\)。
平衡树
平衡的二叉搜索树。
当一棵二叉搜索树 "大致" 平衡时,访问任意一个节点的时间复杂度是 \(O(log_n)\)的。
平衡树的实现
Treap
相对于其他的平衡树这种树的平衡性较弱,常数也比较大,因此已经没有人还在用这种做法了,构造需要用到随机数进行堆的维护,来满足平衡,增删操作就是将这一棵树进行旋转。推荐指数 \((0/10)\)
Splay
相对与 \(Treap\) 有了好的改良,关键词就是三点一线,如果出现了三个点连成了一条只线就把中间的点提起来但父节点,依旧满足二叉搜索树的性质,同时深度也减小了一层。推荐指数 \((8/10)\)
FHQ-Treap
之所以它会有这个 \(Treap\),是因为它借用了 \(Treap\) 的随机数构造的思想,同时他除了这个名字还被称为无旋平衡树,顾名思义就是不会旋转,它的主要操作分为两种:分裂(\(split\)),合并(\(merge\)),其余操作就是基于这两种操作。推荐指数\((\infty ^\infty /10)\)。
可谓是非常好用。
今天讲的就是这个FHQ-Treap
节点的构造
struct no{
int val,rnk,ls,rs,siz;//权值,构造用的随机数,左节点,右节点,子树大小
};
非常的朴素,如果随机数使用 rand() 函数需要在主函数里面添加一行srand(time(0))//创建一个随即库
新建一个节点
int add_node(int val){
tot++;//节点的编号
t[tot]={val,rand(),0,0,1};
return tot;
}
重点
分裂与合并操作
//分裂
void split(int u,int &a,int &b,int val){
if(!u){//没有节点分
a=b=0;
return;
}
if(t[u].val<=val){//按照权值进行分裂
a=u;
split(t[u].rs,t[u].rs,b,val);
}else{
b=u;
split(t[u].ls,a,t[u].ls,val);
}
update(u);
return;
}
//合并
void merge(int &u,int a,int b){//由于会有删除操作,所以需要根节点会有修改,u就表示根节点
if(!a || !b){//只有一个节点就选你当根,也不必在往下分了
u=a+b;
return;
}
if(t[a].rnk<t[b].rnk){//按照随机值进行合并,达到平衡
u=a;
merge(t[a].rs,t[a].rs,b);
}else{
u=b;
merge(t[b].ls,a,t[b].ls);
}
update(u);
return;
}
其中的update表示对节点的子树大小修改
void update(int u){
t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
return;
}
一些基本操作
//插入
void insert(int &u,int val){
int a=0,b=0,k=add_node(val);
split(u,a,b,val);
merge(a,a,k);
merge(u,a,b);
return;
}
//删除
void del(int &u,int val){
int a=0,b=0,z=0;
split(u,a,b,val);
split(a,a,z,val-1);
merge(z,t[z].ls,t[z].rs);
merge(a,a,z);
merge(u,a,b);
return;
}
//寻找排名为第x的数
int find_num(int k,int x){
while(t[t[k].ls].siz+1!=x){
if(t[t[k].ls].siz>=x){
k=t[k].ls;
}else{
x-=t[t[k].ls].siz+1;
k=t[k].rs;
}
}
return t[k].val;
}
//寻找大小为x的数的排名
int find_rank(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=t[a].siz+1;
merge(k,a,b);
return tmp;
}
//前驱
int prev(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=find_num(a,t[a].siz);
merge(k,a,b);
return tmp;
}
//后继
int nxtv(int &k,int val){
int a=0,b=0;
split(k,a,b,val);
int tmp=find_num(b,1);
merge(k,a,b);
return tmp;
}
还是比较简单的
P3369 【模板】普通平衡树
将上面的操作东拼拼西凑凑就好了
#include<bits/stdc++.h>
using namespace std;
int root;
int tot;
struct no{
int val,rnk,ls,rs,siz;
}t[100005];
void update(int u){
t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
return;
}
int add_node(int val){
tot++;
t[tot]={val,rand(),0,0,1};
return tot;
}
void split(int u,int &a,int &b,int val){
if(!u){
a=b=0;
return;
}
if(t[u].val<=val){
a=u;
split(t[u].rs,t[u].rs,b,val);
}else{
b=u;
split(t[u].ls,a,t[u].ls,val);
}
update(u);
return;
}
void merge(int &u,int a,int b){
if(!a || !b){
u=a+b;
return;
}
if(t[a].rnk<t[b].rnk){
u=a;
merge(t[a].rs,t[a].rs,b);
}else{
u=b;
merge(t[b].ls,a,t[b].ls);
}
update(u);
return;
}
void insert(int &u,int val){
int a=0,b=0,k=add_node(val);
split(u,a,b,val);
merge(a,a,k);
merge(u,a,b);
return;
}
void del(int &u,int val){
int a=0,b=0,z=0;
split(u,a,b,val);
split(a,a,z,val-1);
merge(z,t[z].ls,t[z].rs);
merge(a,a,z);
merge(u,a,b);
return;
}
int find_num(int k,int x){
while(t[t[k].ls].siz+1!=x){
if(t[t[k].ls].siz>=x){
k=t[k].ls;
}else{
x-=t[t[k].ls].siz+1;
k=t[k].rs;
}
}
return t[k].val;
}
int find_rank(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=t[a].siz+1;
merge(k,a,b);
return tmp;
}
int prev(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=find_num(a,t[a].siz);
merge(k,a,b);
return tmp;
}
int nxtv(int &k,int val){
int a=0,b=0;
split(k,a,b,val);
int tmp=find_num(b,1);
merge(k,a,b);
return tmp;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
srand(time(0));
int T;
cin>>T;
root=0;
while(T--){
int op,val;
cin>>op>>val;
if(op==1){
insert(root,val);
}else if(op==2){
del(root,val);
}else if(op==3){
cout<<find_rank(root,val)<<"\n";
}else if(op==4){
cout<<find_num(root,val)<<"\n";
}else if(op==5){
cout<<prev(root,val)<<"\n";
}else{
cout<<nxtv(root,val)<<"\n";
}
}
return 0;
}
P2234 [HNOI2002] 营业额统计
找寻前驱与后继哪一个的权值与它的差更小,通过平衡树实现,需要注意安插两个哨兵,不然会越界。
#include<bits/stdc++.h>
using namespace std;
int root;
int tot;
struct no{
int val,rnk,ls,rs,siz;
}t[40005];
int cnt[2000005];
void update(int u){
t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
return;
}
int add_node(int val){
tot++;
t[tot]={val,rand(),0,0,1};
return tot;
}
void split(int u,int &a,int &b,int val){
if(!u){
a=b=0;
return;
}
if(t[u].val<=val){
a=u;
split(t[u].rs,t[u].rs,b,val);
}else{
b=u;
split(t[u].ls,a,t[u].ls,val);
}
update(u);
return;
}
void merge(int &u,int a,int b){
if(!a || !b){
u=a+b;
return;
}
if(t[a].rnk<t[b].rnk){
u=a;
merge(t[a].rs,t[a].rs,b);
}else{
u=b;
merge(t[b].ls,a,t[b].ls);
}
update(u);
return;
}
void insert(int &u,int val){
int a=0,b=0,k=add_node(val);
split(u,a,b,val);
merge(a,a,k);
merge(u,a,b);
return;
}
void del(int &u,int val){
int a=0,b=0,z=0;
split(u,a,b,val);
split(a,a,z,val-1);
merge(z,t[z].ls,t[z].rs);
merge(a,a,z);
merge(u,a,b);
return;
}
int find_num(int k,int x){
while(t[t[k].ls].siz+1!=x){
if(t[t[k].ls].siz>=x){
k=t[k].ls;
}else{
x-=t[t[k].ls].siz+1;
k=t[k].rs;
}
}
return t[k].val;
}
int find_rank(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=t[a].siz+1;
merge(k,a,b);
return tmp;
}
int prev(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
if(!a){
return -1e9;
}
int tmp=find_num(a,t[a].siz);
merge(k,a,b);
return tmp;
}
int nxtv(int &k,int val){
int a=0,b=0;
split(k,a,b,val);
if(!b){
return 1e9;
}
int tmp=find_num(b,1);
merge(k,a,b);
return tmp;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
srand(time(0));
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++){
int a;
cin>>a;
if(cnt[a+1000000]){
continue;
}
cnt[a+1000000]++;
if(i==1){
sum+=a;
insert(root,a);
continue;
}
sum+=min(a-prev(root,a),nxtv(root,a)-a);
insert(root,a);
}
cout<<sum;
return 0;
}
P1486 [NOI2004] 郁闷的出纳员
用一个 \(tag\) 统计当前增减了多少工资,则每个人的实际工资就是初始工资加上 \(tag\),新员工不用前面的增减,所以他的初始工资应该为输入工资减去 \(tag\)。
由于有员工会离家出走,所以需要在降工资的时候把初始工资 \(\le\) 最低工资 \(-tag-1\) 的员工给踢掉,他们一定时工资最小的一批,所以用分裂把他们分裂掉就好了。
#include<bits/stdc++.h>
using namespace std;
int root;
int tot;
int n,m;
int tag;
int cnt;
struct no{
int val,rnk,ls,rs,siz;
}t[300005];
void update(int u){
t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
return;
}
int add_node(int val){
tot++;
t[tot]={val,rand(),0,0,1};
return tot;
}
void split(int u,int &a,int &b,int val){
if(!u){
a=b=0;
return;
}
if(t[u].val<=val){
a=u;
split(t[u].rs,t[u].rs,b,val);
}else{
b=u;
split(t[u].ls,a,t[u].ls,val);
}
update(u);
return;
}
void merge(int &u,int a,int b){
if(!a || !b){
u=a+b;
return;
}
if(t[a].rnk<t[b].rnk){
u=a;
merge(t[a].rs,t[a].rs,b);
}else{
u=b;
merge(t[b].ls,a,t[b].ls);
}
update(u);
return;
}
void insert(int &u,int val){
int a=0,b=0,k=add_node(val);
split(u,a,b,val);
merge(a,a,k);
merge(u,a,b);
return;
}
void del(int &u,int val){
int a=0,b=0,z=0;
split(u,a,b,val);
split(a,a,z,val-1);
merge(z,t[z].ls,t[z].rs);
merge(a,a,z);
merge(u,a,b);
return;
}
void fire(int &u){
int a=0,b=0;
split(u,a,b,m-tag-1);
u=b;
cnt+=t[a].siz;
return;
}
int find_num(int k,int x){
while(t[t[k].ls].siz+1!=x){
if(t[t[k].ls].siz>=x){
k=t[k].ls;
}else{
x-=t[t[k].ls].siz+1;
k=t[k].rs;
}
}
return t[k].val;
}
int find_rank(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=t[a].siz+1;
merge(k,a,b);
return tmp;
}
int prev(int &k,int val){
int a=0,b=0;
split(k,a,b,val-1);
int tmp=find_num(a,t[a].siz);
merge(k,a,b);
return tmp;
}
int nxtv(int &k,int val){
int a=0,b=0;
split(k,a,b,val);
int tmp=find_num(b,1);
merge(k,a,b);
return tmp;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
srand(time(0));
cin>>n>>m;
for(int i=1;i<=n;i++){
char op;
int k;
cin>>op>>k;
if(op=='I'){
if(k<m){
continue;
}
insert(root,k-tag);
}else if(op=='A'){
tag+=k;
}else if(op=='S'){
tag-=k;
fire(root);
}else{
if(k>t[root].siz){
cout<<-1<<"\n";
continue;
}
cout<<find_num(root,t[root].siz-k+1)+tag<<"\n";
}
}
cout<<cnt;
return 0;
}
P2286 [HNOI2004] 宠物收养场
抓住关键同一时间呆在收养所中的,要么全是宠物,要么全是领养者,因此不用写两棵平衡树,只需记录一下当前平衡树里是宠物还是领养者,依旧寻找前驱后继,\(so\ easy\)。
#include<bits/stdc++.h>
using namespace std;
struct no{
int val,rnk,ls,rs,siz;
}t[80005];
const int mod=1000000;
int tot,root;
void update(int u){
t[u].siz=t[t[u].ls].siz+t[t[u].rs].siz+1;
return;
}
int add_node(int val){
tot++;
t[tot]={val,rand(),0,0,1};
return tot;
}
void split(int u,int &a,int &b,int val){
if(!u){
a=b=0;
return;
}
if(t[u].val<=val){
a=u;
split(t[u].rs,t[u].rs,b,val);
}else{
b=u;
split(t[u].ls,a,t[u].ls,val);
}
update(u);
return;
}
void merge(int &u,int a,int b){
if(!a || !b){
u=a+b;
return;
}
if(t[a].rnk<t[b].rnk){
u=a;
merge(t[a].rs,t[a].rs,b);
}else{
u=b;
merge(t[b].ls,a,t[b].ls);
}
update(u);
return;
}
void insert(int &u,int val){
int a=0,b=0,c=add_node(val);
split(u,a,b,val);
merge(a,a,c);
merge(u,a,b);
return;
}
void del(int &u,int val){
int a=0,b=0,z=0;
split(u,a,b,val);
split(a,a,z,val-1);
merge(z,t[z].ls,t[z].rs);
merge(a,a,z);
merge(u,a,b);
return;
}
int find_rnk(int &u,int val){
int a=0,b=0;
split(u,a,b,val-1);
int tmp=t[a].siz+1;
merge(u,a,b);
return tmp;
}
int find_num(int u,int x){
while(t[t[u].ls].siz+1!=x){
if(t[t[u].ls].siz>=x){
u=t[u].ls;
}else{
x-=t[t[u].ls].siz+1;
u=t[u].rs;
}
}
return t[u].val;
}
int prev(int &u,int val){
int a=0,b=0;
split(u,a,b,val-1);
if(!a){
return -1e9;
}
int tmp=find_num(a,t[a].siz);
merge(u,a,b);
return tmp;
}
int nxtv(int &u,int val){
int a=0,b=0;
split(u,a,b,val);
if(!b){
return 1e9;
}
int tmp=find_num(b,1);
merge(u,a,b);
return tmp;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
srand(time(0));
int n;
cin>>n;
int f=0,sum=0;
for(int i=1;i<=n;i++){
int a,b;
cin>>a>>b;
if(!tot || t[root].siz==0){
f=a;
insert(root,b);
}else if(a==f){
insert(root,b);
}else{
int las=prev(root,b+1),nxt=nxtv(root,b);
if(b-las<=nxt-b){
sum=(sum+(b-las))%mod;
del(root,las);
}else{
sum=(sum+(nxt-b))%mod;
del(root,nxt);
}
}
}
cout<<sum;
return 0;
}

浙公网安备 33010602011771号