文章目录
算法简介
树套树,一种强大毒瘤的数据结构。因为嵌套的缘故,导致代码又难写,又难调。不过随之而来的,是其强大的能力。树套树的种类非常多,很多的树形结构都能互相嵌套。本文介绍其中最常见的一种,线段树套平衡树。(本文以树套树的板题P3380 【模板】二逼平衡树为基准)
思路与实现
空间复杂度
每种树套树因为所套的树不同,时间与空间上通常有很大的差异,一般会相差一两个 l o g log log ,而线段树套平衡树,很显然,因为线段树一共有 l o g n log n logn 层,每一层的节点个数都是 n 。所以一共有 n l o g n nlogn nlogn 个节点,而平衡树是对每个节点建立话费一点空间,所以总的空间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn) 。
实现(+时间复杂度分析)
而时间复杂度的话,不同操作差异很大,我们分到每个操作里去分析。
修改位置 k 上的值
因为我们需要修改所有包含这个节点的平衡树,所以一共会修改这条链上的所有点。一共 l o g n logn logn 次。而每次平衡树修改的时间复杂度也是 l o g n logn logn 。所以总的时间复杂度为 l o g 2 n log^2n log2n。
void change(int rt,int l,int r,int x,int k){
int mid=(l+r)>>1;
FT[rt].outint(a[x]);//删除 x位置上的数
FT[rt].init(k);//插入k
if(l==r)return ;//到叶子节点了,结束
if(x<=mid)change(rt<<1,l,mid,x,k);//在左子树
else change(rt<<1|1,mid+1,r,x,k);//要修改一条链上的所有点
return ;
}
查询 k 在区间内的排名
因为是查询区间排名。那我们肯定要先在线段树中查询到这个区间。然后每个区间查询比k小的数有多少,把值加起来就可以了。总的复杂度就是 O ( l o g 2 n ) O(log^2n) O(log2n)
int find_rank(int rt,int l,int r,int L,int R,int k){
//查询排名
int ans=0;
if(L<=l&&r<=R){
//在查询区间
ans=FT[rt].findrank(k);
return ans;//有几个比它小
}
int mid=(l+r)>>1;
if(L<=mid)ans+=find_rank(rt<<1,l,mid,L,R,k);
if(mid+1<=R)ans+=find_rank(rt<<1|1,mid+1,r,L,R,k);
return ans;
}
查询区间排名为 k 的数
显示跟上一问一样,我们还是要先找到这个区间,但是我们发现区间排名为 k 的数这个问题并不能像上一问一样合并两个区间的答案。所以我们只能在最前面在加上一个二分。因为区间排名为 k 的数显然有单调性。值越大,显然排名越大。因为加了一个二分,所以时间复杂度变为了 O ( l o g 3 n ) O(log^3n) O(log3n)
int find_k(int l,int r,int rank){
//找排名为k的数
int a=0,b=1e8,mid,sum,ans=-1;
mid=(a+b)>>1;
while(a<=b){
//使用二分查找,因为数越大,排名越高
mid=(a+b)>>1;
sum=find_rank(1,1,n,l,r,mid);
if(sum+1<=rank){
ans=mid;
a=mid+1;//排名小了