平衡树Treap

treap:

treap=tree+heap,树+堆

也就是说,这个东西是个树,但是满足堆的性质。

前置知识:

BST二叉搜索树:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。

也就是说,你把它从根节点中序遍历一边就能得到一个从小到大的数列。

大概长这样子:

对于4:左边子树节点的权值为0 1 2 3,都比4小,右边子树节点的权值为5 6 7,都比4大。

对于1:左边子树节点权值为0,比1小,右边子树节点权值为2 3,比1大(且比4小)。

对于其他节点同理。

一个不难理解的东西。

堆:

priority_queue。。。

堆中某个节点的值总是不大于或不小于其父节点的值;

这里就不再手写堆了,‘’

回过头来:

根据BST的定义我们可以知道,任意选取一个数做为根节点,在整个数列中一定能找到比他小的数和比他大的数(或者其中一个不存在),同样的,对于它的子树也是根节点不确定的。

有些毒瘤! 出题人会拿duliu数据把BST卡成一条链,然后多次询问,复杂度直接爆炸。

那么为了均摊一下BST的深度,使得每次询问时间复杂度都接近logn的话,我们需要对它进行一系列操作:

赋权值:

我们随机为每一个节点赋值一个权值pri,对于pri我们要求pri低的深度小,也就是小根堆的性质,因为rand每次都随机的话是可以均摊一下节点的深度(具体的原理我还一时半会说不清。。感性李姐)

如何调整节点使得其满足堆序:

在节点插入时,和bst一样建立,找到自己节点应该在的位置,当当前节点为空时,建立新节点,rand它的pri后返回,对其进行左右旋转操作。

左旋:就是把根节点转移到其左子节点的位置,并维护BST。

方法:直接上图:

我们可以按照红线的方法来划分子树;

这样就是上面的一条链和下面的又子节点的子树两部分了。

旋转后,绿色节点到达根节点位置,也就是把整条链往左拽了一下;子树换了父亲,认左边的节点为爸爸了:

就这样完了。

右旋:换个方向搞左旋。

删除节点:

删除时,如果该节点对应size>1,则size--,否则将其权值pri赋为inf或者清空。之后我们可以通过比较左右子树pri的大小决定谁做新的根(为了满足堆的性质,单数不管是左子树根节点还是右子树根节点做新的根,都不会影响BST性质,感性模拟),之后根节点就变成了原来左子树/右子树中的一员,继续递归下去,直到成为叶节点,也就是这一段开头说的那句话的情况。之后我们就可以直接删除它了。

求前驱后继:

一个数x的前驱定义为小于这个数的最大的数。后继就是大于等于这个数的最小的数。

理解如何递归:

以前驱举例子,因为要找小于这个数的最大的数,那么肯定到左子树去找;

如果当前节点大于等于根节点的值,到左子树里面找;

如果当前节点空的,返回inf,

else,如果当前节点的val<x 则返回max(v,去右子树递归的返回值);

else,说明答案还在左子树里面,那么就到左子树去找。

原理基本讲解完毕。

例题 :【模板】普通平衡树

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
using namespace std;
#define lovelive long long
int tot;
int pos[100020];
int p[100020];
map <int,int> s;
priority_queue<int,vector<int>,greater<int> > so;
struct question
{
  int num,x;
}q[100020];
struct tree
{
  int l,r,sum;
}t[400020];
void buildtree(int i,int l,int r)
{
  t[i].l=l;
  t[i].r=r;
  if(l==r)
  {
      pos[l]=i;
    return ;
  }
  int mid=(l+r)>>1;
  buildtree(i<<1,l,mid);
  buildtree(i<<1|1,mid+1,r);
}
void insert(int i,int point)
{
  t[i].sum++;
  int mid=(t[i].l+t[i].r)>>1;
  if(t[i].l==t[i].r)
    return ;
  if(point>mid)
    insert(i<<1|1,point);
  else
    insert(i<<1,point);
}
void Delete(int i,int point)
{
  t[i].sum--;
  int mid=(t[i].l+t[i].r)>>1;
  if(t[i].l==t[i].r)
    return ;
  if(point>mid)
    Delete(i<<1|1,point);
  else
    Delete(i<<1,point);
}
int find_num(int i,int x)
{
  if(t[i].l==t[i].r)
    return t[i].l;
  if(x>t[i<<1].sum)
    find_num(i<<1|1,x-t[i<<1].sum);
  else
    find_num(i<<1,x);
}
int query(int i,int l,int r)
{
  if(r<t[i].l||l>t[i].r)
    return 0;
  if(r>=t[i].r&&l<=t[i].l)
    return t[i].sum;
  return query(i<<1,l,r)+query(i<<1|1,l,r);
}
int find_rank(int x)
{
  return query(1,1,x-1)+1;
}
int find_pre(int x)
{
  int p=find_rank(x);
  return find_num(1,p-1);
}
int find_next(int x)
{
  int p=find_rank(x);
  return find_num(1,p+t[pos[x]].sum);
}
int main()
{
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
    {
      scanf("%d%d",&q[i].num,&q[i].x);
      if(q[i].num!=4) 
        so.push(q[i].x);
    }
  int point = -2e9-1;
  while(!so.empty())
  {
      if(so.top()!=point)
        ++tot;
      s[so.top()]=tot;
      p[tot]=so.top();
      point=so.top();
      so.pop();
  }  
  buildtree(1,1,tot);
  for(int i=1;i<=n;i++)
    if(q[i].num!=4)
        q[i].x=s[q[i].x];
  for(int i=1;i<=n;i++)
  {
      switch(q[i].num)
      {
        case 1:insert(1,q[i].x);break;
        case 2:Delete(1,q[i].x);break;
        case 3:cout<<find_rank(q[i].x)<<"\n";break;
        case 4:cout<<p[find_num(1,q[i].x)]<<"\n";break;
        case 5:cout<<p[find_pre(q[i].x)]<<"\n";break;
        case 6:cout<<p[find_next(q[i].x)]<<"\n";break;
    }
  }
  return 0;
}
posted @ 2022-03-06 21:22  PassName  阅读(59)  评论(0编辑  收藏  举报