平衡树学习笔记
Stars_visitor_tyw
·
2025-02-16 14:08:39
·
算法·理论
平衡树
通常是指二叉搜索平衡树。
概念解释——二叉搜索树
对于一棵二叉树,每个点 i 有权值 w_i,对于任意点 x,其左子树的权值都不超过 w_x,其右子树的权值都大于 w_x。
我们常常将一个序列的下标当作权值构建二叉搜索树,这样树的中序遍历就是一段连续的区间。
平衡树的广泛定义
是指一棵树型结构,对任意结点 x,其左右子树的高度差不超过 1。
算法竞赛中平衡树的实现
treap,随机,左旋和右旋。
splay,贪心,伸展和旋转。
fhq_treap(无旋 treap),分裂+合并。
fhq_treap 的实现
分裂操作,split。
给定一棵平衡树 k,按照某个权值 val,分裂成平衡树 a 和 b,且 a 树所有点的权值不超过 val,b 树的所有点权大于 val。
合并操作,merge。
将两棵平衡树 a 和 b 合并成一棵平衡树 k,且保证 a 树的点权都小于 b 树的点权。
代码
#include
#define int long long
using namespace std;
const int INT=1145141919810;
#define N 500005
int n, tot, root;
struct node
{
int val, rnk, lc, rc, siz;
}tree[N];
void pushup(int k)
{
tree[k].siz=tree[tree[k].lc].siz+tree[tree[k].rc].siz+1;
}
int add_node(int val)
{
tree[++tot].siz=1;
tree[tot].val=val;
tree[tot].lc=tree[tot].rc=0;
tree[tot].rnk=rand();
return tot;
}
void split(int k, int &a, int &b, int val)
{
if(!k)
{
a=b=0;
return ;
}
if(tree[k].val<=val)
{
a=k;
split(tree[k].rc,tree[k].rc,b,val);
}
else
{
b=k;
split(tree[k].lc,a,tree[k].lc,val);
}
pushup(k);
}
void merge(int &k, int a, int b)
{
if(!a||!b)
{
k=a+b;
return ;
}
if(tree[a].rnk { k=a; merge(tree[a].rc,tree[a].rc,b); } else { k=b; merge(tree[b].lc,a,tree[b].lc); } pushup(k); } void insert(int &k, int val) { int a=0, b=0, cur=add_node(val); split(k,a,b,val); merge(a,a,cur); merge(k,a,b); return ; } void del(int &k, int val) { int a=0, b=0, z=0; split(k,a,b,val); split(a,a,z,val-1); merge(z,tree[z].lc,tree[z].rc); merge(a,a,z); merge(k,a,b); return ; } int find_rank(int &k, int val) { int a=0, b=0; split(k,a,b,val-1); int tmp=tree[a].siz+1; merge(k,a,b); return tmp; } int find_num(int k ,int x) { while(tree[tree[k].lc].siz+1!=x) { if(tree[tree[k].lc].siz>=x)k=tree[k].lc; else { x-=tree[tree[k].lc].siz+1; k=tree[k].rc; } } return tree[k].val; } int calc_prev(int &k, int val) { int a=0, b=0; split(k,a,b,val-1); int tmp=find_num(a,tree[a].siz); merge(k,a,b); return tmp; } int calc_suf(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() { cin>>n; root=0; srand(time(0)); for(int i=1;i<=n;i++) { int op, val; cin>>op>>val; if(op==1) { insert(root,val); } else if(op==2) { del(root,val); } else if(op==3) { cout< } else if(op==4) { cout< } else if(op==5) { cout< } else if(op==6) { cout< } } } 文艺平衡树 P3391 无旋 treap 按 size 分裂 适用于处理序列的区间问题,比如将数列 [L,R] 翻转。 将下标视为结点的权值,按下标建立平衡树,其中序遍历为连续下标。 split 时,将平衡树分裂成 A 和 B 两棵树,且 a 树的结点恰好为 x 个。 模板思路 以下标为点权,建立平衡树。 对于操作区间 [L,R] 可以将平衡树上对应的子树准确分裂。一个区间的翻转等价于一棵树的左右子树的迭代翻转。 为了节省时间,每个结点维护懒标记 tag_i 表示以 i 为根的子树是否需要翻转。 打标记:tag_i=tag_i \oplus i。下传标记的时间:分裂、合并、输出前。 P3850 书架 把位置和排名对应起来理解。 初始时 n 本书排名 1\sim n 建树。 插入一本书位置为 x 时,分裂 x 个结点,将新的书放在右侧合并。 P3765 总统选举 前置知识——摩尔投票法 求出数列中是否有出现次数超过一半的元素。 维护 ans 表示目标元素,维护 cnt 表示目标元素出现的次数。 伪代码: ... for(int i=1;i<=n;i++) { if(!cnt)ans=a[i],cnt++; else if(a[i]==ans)cnt++; else cnt--; } if(cnt>0)cout< else cout<<"No solution."; ... 思路 利用摩尔投票在有解时,可以找到超过一半的元素。 利用线段树分治维护摩尔投票的信息,对于一个结点 cur,其左右子树若 ans1==ans2,则 ans=ans1+ans2,cnt=cnt1+cnt2。否则 ans=cnt1>cnt2?ans1:ans2,cnt=abs(cnt1-cnt2)。 如何确保区间内找到的 ans 确实超过了一半? 对于每个候选人 i,维护一棵平衡树 tr_i 表示投票给 i 的人,按下标左小右大构建。 对于询问 [L,R],利用线段树得到众数 ans,找到 ans 对应的平衡树,分裂出 [L,R] 的部分,设有 size 结点,若 2\times size>(R-L+1),则 ans 超过一半。 对于修改操作,直接将结点从一棵树 merge 到另一棵树。 P5217 贫穷 I 操作,tot++,插入结点,编号和点权 tot。 D 操作,split 前 x 个,split 前 x-1 个,丢掉。 R 操作,参照文艺平衡树,打标记,翻转。 P 操作,找排名为 x 的元素,从根结点开始,要么往左,要么往右,找到 x 后,取出当前平衡树中 x 的左子树的大小。 T 操作,分裂 x,再分裂 x-1。 Q 操作,考虑到字母只有 26 个,状压维护。对于平衡树结点 cur,buc[cur]=buc[lc]|buc[rc]。