FHQ_Treap 基础

AGOH B站讲解,三分钟入门,1天熟练,2天持久化

可持久化讲解博客: KevinYu

随机化的修改与玄学骗分

xxx的fhq讲解

万万没想到—FHQ一本通

一, 原理介绍

  • 免旋,保存原结构的基础上进行增删,使得持久化成为可能
  • 核心操作: split,merge (有点类似于左偏树)
  • 把树按照某种标准(子树大小和特定权值)分成几部分,对它(需要操作的分裂子树)进行操作
  • 与traep的联系:按照随机权值合并子树,(但是可是)只有一处用到随机化

二,关键操作

1,节点创建:create

struct node
{
    int l,r;
    int key,siz;
    ll sum,v;
    #define t(x) tr[x]
    #define ls(x) tr[x].l
    #define rs(x) tr[x].r
    void init (ll val)
    {
        v=sum=val;
        siz= 1;
        l=r=0;
    } 
}

int create (ll val)
{
    t(++tot).init (val);
    return tot;
}

2,节点复制(不仅仅是权值):copy

int copy (int tar)
{
    t(++tot) = t(tar);
    return tot;
}

3,上传:up

void up(int x)
{
    t(x).siz = t((ls(x))).siz + t(rs(x)).siz +1;
    t(x).sum = t(ls(x)).sum + t(rs(x)).sum + t(x).v;
}

4,下传:down

void down(int x)
{
    if(t(x).tag==0)return;
    if(ls(x)) ls(x)= copy(ls(x));
    if(rs(x)) rs(x)= copy(rs(x));
    swap(ls(x),rs(x));
    if(ls(x)) t(ls(x)).tag ^= 1;
    if(rs(x)) t(rs(x)).tag ^= 1;
    t(x).tag = 0;
}

5,分裂子树:split

  • 将原区间 ( l , r ) (l,r) (l,r)分裂成 ( l , q u e r y v a l ) (l,queryval) (l,queryval) ( q u e r y v a l + 1 , r ) (queryval+1,r) (queryval+1,r)两个区间树
  • x x x 就是左区间树的根节点, y y y 就是右区间树的根节点
  • 不论 x x x y y y的初始值是什么,因为分裂操作是用的 & \& & 这一可以赋值的变量
  • 最终的 x x x 不是空节点0就是左区间树的根节点, y y y 不是空节点 0 就是右区间树的根节点。
void split (int p,int k,int &x,int &y)
{
    if(p==0){x=y=0; return ;}
    down(p);
    if(t(ls(p)).siz <k)
    {
        x= copy (p);
        split (rs(x),k-t(ls(p)).siz-1,rs(x),y);
        up(x);
    }
    else 
    {
        y= copy (p);
        split (ls(y),k,x,ls(y));
        up(y);
    }
}
void split (int p,int val,int &x,int &y)
{
    if(p==0) {x=y= 0;return;}

    if(t(p).v<=val) x=p ,split(rs(x),val,rs(x),y);
    else y=p ,split(ls(y),val,x,ls(y));
    up(p);
}

6,合并子树:merge

  • 保证树上靠左的在函树的左参数,右边亦之(相对位置对应)
  • 注意返回值要返回~!!!
int merge (int x,int y)
{
    if(x==0 || y==0)return x+y;
    down(x); down(y);
    if(t(x).key<t(y).key)
    {
        rs(x)= merge (rs(x),y);
        up(x);
        return x;
    }
    else
    {
        ls(y)= merge (x,ls(y));
        up(y);
        return y;
    }
}

四,可持久化

思想概述:

  • 根数组,每一个版本开一个根,没变就复制,否则开新的根
  • 子结构不变: 保证树的空间结构在不同版本是一定的,(无旋)
  • 动态开点(这是一种思维):改了哪个点,就把从这个点到根的所有包涵的节点开新的备份并修改成需要的,其余的保持不变

实现技巧:

  • 带根修改法:传根的参的时候带引用,修改扩入函数
  • 复制待修改的节点,在赋值到特定对象,最后借助复制的带修节点更新子树信息

一,可持久数组

  • 只修不删,直接借助主席树思路
  • 抛弃key的堆性质,只要子树大小(位置)和值

i,建树(主席树在二叉树的拓展)

int build (int l,int r)
{
    if(l>r)return 0;
    int mid = (l+r)>>1;
    int ret=++tot;
    t(ret).init(a[mid]);
    ls(ret)=build (l,mid-1);
    rs(ret)=build (mid+1,r);
    up(ret);
    return ret;
}

ii,修改

  • 思路理解版
  • nt(新的节点),ot(旧的递归到的,需要修改和复制的)
void modify(int ot,int &nt,int k,int val)
{
	nt=++tot;
  	t(nt).v= t(ot).v;
  	t(nt).siz= t(ot).siz ;
  
  	if(tls(ot).siz+1==k)
  	{
  		t(nt).v= val;
  		ls(nt)=ls(ot);
  		rs(nt)=rs(ot);
  		return ;
  	}
  
  	else if(tls(ot).siz>=k)
  	{
  		rs(nt)=rs(ot);
  		modify(ls(ot),ls(nt),k,val);
  	}
  
  	else
  	{
  		ls(nt)=ls(ot);
  		modify(rs(ot),rs(nt),k-tls(ot).siz-1,val);
  	}
}
  • 竞赛集成版:
void modify(int ot,int &nt,int k,int val)
{
    nt=copy_node (ot);
    if(tls(nt).siz+1==k)
    {
        t(nt).v=val;
        return;
    }
    else if(tls(nt).siz>=k) modify(ls(nt),ls(nt),k,val);    
    else modify(rs(nt),rs(nt),k-tls(nt).siz-1,val);
}

二,普通平衡树,文艺平衡树

void split (int p,int val,int &x,int &y)
{
    if(p==0)
    {
        x=y= 0;
        return;
    }

    p=copy_node (p);

    if(t(p).v<=val)
    {
        x=p;
        split(rs(x),val,rs(x),y);
        up(x);
    }

    else
    {
        y=p;
        split(ls(y),val,x,ls(y));
        up(y);
    }
}

int merge (int x,int y)
{
    if(x==0||y==0)return x+y;

    if(t(x).key<t(y).key)
    {
        x=copy_node (x);
        rs(x)=merge(rs(x),y);
        up(x);
        return x;
    }

    else
    {
        y=copy_node (y);
        ls(y)= merge(x,ls(y));
        up(y);
        return y;
    }
}

小技巧

1,笛卡尔线性建树

  • 维护treap的最右链,保证堆性质,我们考虑按照序列从左到右的顺序,也是中序遍历的顺序加入点,那么加入之后这个点一定会在最右链上。
  • 那么我们考虑用栈存储这个最右链,那么我们需要一直弹栈,直到栈空或者找到新加点的修正值大于栈顶的修正值,即满足小根堆的性质。
  • 弹出的部分最堆的时候需要在当前节点以下,在遍历的时候要在当前节点的前面,所以接到左子树
  • 新进的节点必然比栈顶key大,且在后面被中序遍历,直接接到右子树
int build()
{
    int last,x,top;
    top=0;
    for(int i=1;i<=n;i++)
    {
        x=new_node(a[i]);last =0;
        while(top && t(s[top]).v>t(x).v)
        {
            up(last = s[top]);
            s[top--]=0;
        }
        if(top) t(s[top]).r= x;
        t(x).l = last ; s[++top]= x;
    }
    while (top) up(s[top--]);
    return s[1]; //返回根节点
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值