fhq Treap

引入:

平时的 \(treap\) 又长又需要旋转,而且不能求区间序列问题
因此我们用 \(fhqtreap\) 解决。

数组定义:

int ch[MAXN][3];//0 左孩子,1右孩子
int val[MAXN];//每个点的权值
int rnd[MAXN];//每个点的随机权值
int size[MAXN];//以每个点为根的树的大小

更新:

inline void update(int x){
    size[x]=1+size[ch[x][0]]+size[ch[x][1]];
}

分裂:

一共有两种分裂:

权值分裂:

当我们遍历到一个节点时,如果它的权值小于 \(k\),那么它的左子树会被分到左边的树里,然后我们遍历它的右儿子,如果大于 \(k\) ,则把它的右子树分到右边的树里。

那么到达递归边界怎么办呢? 有两种情况:

  1. \(root=0\),要给 \(x=y=0\) 初始化.
  2. 分裂到了叶子节点,直接返回。

代码:

void split(int now,int k,int &x,int &y){
    if(!now) x=y=0;
    else if(val[now]<=k){x=now; split(ch[now][1],k,ch[now][1],y);}
    else{y=now;split(ch[now][0],k,x,ch[now][0]);}
    update(now);//update(i)为更新size[i]大小的函数
}

排名分裂:

排名分裂与权值分裂类似,即将前 \(k\) 个放在一颗树里,其余的放在另一棵树里:

void split(int now,int k,int &x,int &y){
    if(!now) x=y=0;
    else{
        if(k<=siz[ch[now][0]]){ y=now; split(ch[now][0],k,x,ch[now][0]);}
        else{x=now;split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y);}
        update(now);
    }
}

合并:

我们假设第一棵树的权值小于第二棵树的权值,我们就可以比较它们随机权值,如果 \(rnd[l]<rnd[r]\) 那么保留它的左子树,另一棵位右子树,否则相反。

int merge(int A,int B){
    if(!A||!B) return A+B;
    if(rnd[A]<rnd[B]){
        ch[A][1]=merge(ch[A][1],B);
        update(A);
        return A;
    }
    else{
        ch[B][0]=merge(A,ch[B][0]);
        update(B);
        return B;
    } 
}

操作:

插入♂:

直接暴力:

int new_node(int a)//新建一个节点
{
    size[++cnt]=1;
    val[cnt]=a;
    rnd[cnt]=rand();
    return cnt;
}
void insert(int a)//插入 
{
    spilt(root,a,x,y);
    root=merge(merge(x,new_node(a)),y);
}

删除:

删除权值为 \(v\) 的点:

  1. 先把整颗树以 \(v\) 为权值 \(split\) 成两棵树 \(a,b\)。
  2. 再把 \(a\) 树按照 \(v-1\) 分成 \(c,d\)。这时候值为 \(v\) 的点一定为 \(d\) 的根。
  3. 那么我们把 \(d\) 的两个子儿子 \(merge\) 起来(这一步就是去除掉 \(v\) 的影响)。
  4. 再把他们重新 \(merge\) 起来得到一个新的树。
void del(int a){
    split(root,a,x,z);
    split(x,a-1,x,y);
    y=merge(ch[y][0],ch[y][1]);
    root=merge(merge(x,y),z);
}

排名:

直接按照 \(a-1\)的权值把树分开,那么 \(x\) 树中最大的应该小于等于 $ a-1$ ,那么 \(a\) 的排名就是 \(size[x]+1\) 。

int myrank(int a){
    split(root,a-1,x,y);
    int res=size[x]-1;
    root=merge(x,y);
    return res;
}

第 \(k\) 小值

int findkth(int x){return val[myrank(root,x)];}

前驱后继

我们先看前驱,因为要小于 \(a\),所以我们还是按照 \(a-1\) 的权值划分 \(x\) 。

现在 \(x\) 中最大的数一定小于等于 \(a-1\) ,所以我们直接输出 \(x\) 中最大的数就好。

后继同理。

int pre(int a){//前驱
    split(root,a-1,x,y);
    int res=val[findKth(x,siz[x])];
    root=merge(x,y);
    return res;
}
int nxt(int a){//后继
    split(root,a,x,y);
    int res=val[findKth(y,1)];
    root=merge(x,y);
    return res;
}

区间问题:

查询一个区间 \([l, r]\) 就把一棵树 \(split\) 成三棵树,查中间那棵,再把它们 \(merge\) 回去。

void add(int l,int r,int delta)//任意操作 
{
    int x,y,z;
    split(root,x,y,r);
    split(x,z,x,l-1);

    addone(x,delta);//任意操作 

    merge(x,z,x);
    merge(root,x,y);
}

可持久化:

如图,每次 \(split\) 和 \(merge\) 走到的所有点都新建一个即可。注意下传标记也要新建点。
fhq treap_treap

例题:

[https://www.luogu.com.cn/problem/P3391](P3391 【模板】文艺平衡树)

# include<iostream>
# include<cstdio>
# include<cstring>
# include<cstdlib>
using namespace std;
const int MAX=1e5+1;
int n,m,tot,rt;
struct Treap{
    int pos[MAX],siz[MAX],w[MAX];
    int son[MAX][2];
    bool fl[MAX];
    void pus(int x)
    {
        siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
    }
    int build(int x)
    {
        w[++tot]=x,siz[tot]=1,pos[tot]=rand();
        return tot;
    }
    void down(int x)
    {
        swap(son[x][0],son[x][1]);
        if(son[x][0]) fl[son[x][0]]^=1;
        if(son[x][1]) fl[son[x][1]]^=1;
        fl[x]=0;
    }
    int merge(int x,int y)
    {
        if(!x||!y) return x+y;
        if(pos[x]<pos[y])
        {
            if(fl[x]) down(x);
            son[x][1]=merge(son[x][1],y);
            pus(x);
            return x;
        }
        if(fl[y]) down(y);
        son[y][0]=merge(x,son[y][0]);
        pus(y);
        return y;
    }
    void split(int i,int k,int &x,int &y)
    {
        if(!i)
        {
            x=y=0;
            return;
        }
        if(fl[i]) down(i);
        if(siz[son[i][0]]<k)
        x=i,split(son[i][1],k-siz[son[i][0]]-1,son[i][1],y);
        else
        y=i,split(son[i][0],k,x,son[i][0]);
        pus(i);
    }
    void coutt(int i)
    {
        if(!i) return;
        if(fl[i]) down(i);
        coutt(son[i][0]);
        printf("%d ",w[i]);
        coutt(son[i][1]);
    }
}Tree;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
      rt=Tree.merge(rt,Tree.build(i));
    for(int i=1;i<=m;i++)
      {
          int l,r,a,b,c;
          scanf("%d%d",&l,&r);
          Tree.split(rt,l-1,a,b);
        Tree.split(b,r-l+1,b,c);
        Tree.fl[b]^=1;
        rt=Tree.merge(a,Tree.merge(b,c));
      }
    Tree.coutt(rt);
    return 0;
}