震惊!一蒟蒻竟然写出fhqTreap

震惊,我竟然写出了fhq Treap

先%fhq大佬

然后%zxy大佬

节点定义

struct node
{
    int w,siz,rdm;//权值,大小(包括自己),随机数
    int l,r;//左右儿子
} nd[MAXN];

特有操作

fhq Treap也被叫做无旋Treap,它通过分裂与合并来维持平衡和堆的性质。

按值分裂

将树分成x,y两颗树,其中x中的元素都小于等于w,y中的元素都大于w。

按地址传参,调用后x,y为新树的根。

开始写的传指针,但我太弱了一直没写对

void splitV(int p,int w,int &x,int &y)//p为当前更新到的节点
{
    if(p==0)//边界
    {
        x=y=0;
    }
    else
    {
        if(nd[p].w<=w)//当前节点比w小,那它和它的左子树都属于x
        {
            x=p;
            splitV(nd[p].r,w,nd[p].r,y);//把x替换为nd[p].r,可以让在右子树中出现的比v小的节点接到x的右边
        }
        else
        {
            y=p;
            splitV(nd[p].l,w,x,nd[p].l);
        }
        update(p);//更新自身的size
    }
}

按节点数量分裂

众所周知,中序遍历一棵BST的结果是排好序的(然而我一开始并没有想到这一点)。所以可以将节点分为前k大的和剩下的两部分。

void splitS(int p,int num,int &x,int &y)
    //把中序遍历的前k个数单独搞成一棵树,剩下的是另一棵树
    //中序遍历得到的是按大小顺序排出的节点。
    //因为会先访问最小的。(先左然后中后右)
{
    if(p==0)
    {
        x=y=0;
    }
    else
    {
        if(nd[nd[p].l].siz>=num)//左边比要的大了
        {
            y=p;
            splitS(nd[p].l,num,x,nd[p].l);//p的左儿子中有比p小,但比第num大大的。
        }
        else
        {
            x=p;
            splitS(nd[p].r,num-nd[nd[p].l].siz-1,nd[p].r,y);//左边小于num,找右边第(num-nd[nd[p].l].siz-1)大的
        }
        update(p);//更新自身大小
    }
}

合并

已知x中所有元素小于等于y中所有元素

合并这两棵树并返回新根。

这里体现了堆的性质

int merge(int x,int y)
        {
            if(x==0||y==0)
            {
                return x+y;//如果其中一个有值,会返回那个值
            }
            else
            {
                int tmp=0;
                if(nd[x].rdm>=nd[y].rdm)//我猜这里可以改成其他的不等关系
                {
                    //x作为根
                    tmp=x;
                    nd[x].r=merge(nd[x].r,y);//合并右儿子与y,然后接到x右边
                }
                else
                {
                    tmp=y;
                    nd[y].l=merge(x,nd[y].l);
                }
                update(tmp);
                return tmp;
            }
        }

平衡树基操

插入

先新建一个节点

int newNd(int x)
{
    ++newp;
    nd[newp].w=x;
    nd[newp].rdm=rand();
    nd[newp].siz=1;
    return newp;
}

按权值分裂,再把新的节点和分开的两棵树按大小合并

void insert(int w)//
{
    int x=0,y=0;
    int p=newNd(w);
    splitV(root,w,x,y);
    x=merge(x,p);
    root=merge(x,y);
}

删除

按w-1,w分成三棵

中间那棵所有节点权值都为w

合并它的左右儿子,丢掉根,就删除了一个节点

记得合回去

void remove(int w)
{
    int x=0,y=0,z=0;
    splitV(root,w,x,y);
    splitV(x,w-1,x,z);
    z=merge(nd[z].l,nd[z].r);
    root=merge(merge(x,z),y); //z 的左右子树合并即可删掉一个v
}

求排名

按值裂开,返回左儿子大小+1

int rank(int w)
{
    int x=0,y=0,ret=0;
    splitV(root,w-1,x,y);
    ret=nd[x].siz+1;
    root=merge(x,y);
    return ret;
}

求第k大

按排名裂开。。。。。

int kth(int k)
{
    int x=0,y=0,z=0;
    splitS(root,k,x,y);
    splitS(x,k-1,x,z);
    int ret=nd[z].w;
    root=merge(merge(x,z),y);
    return ret;
}

前驱

w-1裂开,找小的那棵树里最大的

int front(int w)
{
    int x=0,y=0;
    splitV(root,w-1,x,y);
    int p=x;
    while(nd[p].r)
    {
        p=nd[p].r;
    }
    root=merge(x,y);
    return nd[p].w;
}

后继

w裂开,找大的那棵树里最小的

int back(int w)
{
    int x=0,y=0;
    splitV(root,w,x,y);
    int p=y;
    while(nd[p].l)
    {
        p=nd[p].l;
    }
    root=merge(x,y);
    return nd[p].w;
}

例题:洛谷 P3369 普通平衡树

就是上面那六个操作

代码

区间翻转(延迟标记)

洛谷 P3835 文艺平衡树

翻的时候打lazytag

void fan(int x,int y)
{
    int l,p,r;
    split(root,y,l,r);
    split(l,x-1,l,p);
    nd[p].laz^=1;
    root=merge(merge(l,p),r);
}

split,merge和输出时处理lazytag

void split(int p,int siz,int &x,int &y)
{
    if(p==0)
    {
        x=y=0;
        return;
    }
    if(nd[p].laz)
    {
        push_down(p);
    }
    if(nd[nd[p].l].siz>=siz)
    {
        y=p;
        split(nd[p].l,siz,x,nd[p].l);
    }
    else
    {
        x=p;
        split(nd[p].r,siz-nd[nd[p].l].siz-1,nd[p].r,y);
    }
    update(p);
}

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

    if(nd[x].rdm>nd[y].rdm)
    {
        if(nd[x].laz)push_down(x);
        nd[x].r=merge(nd[x].r,y);
        update(x);
        return x;
    }
    else
    {
        if(nd[y].laz)push_down(y);
        nd[y].l=merge(x,nd[y].l);
        update(y);
        return y;
    }
}

void out(int p)
{
    if(nd[p].laz)
    {
        push_down(p);
    }

    if(nd[p].l)
    {
        out(nd[p].l);
    }
    printf("%d ",nd[p].w);
    if(nd[p].r)
    {
        out(nd[p].r);
    }
}

下传

void push_down(int p)
{
    swap(nd[p].l,nd[p].r);
    nd[p].laz=0;
    nd[nd[p].l].laz^=1;
    nd[nd[p].r].laz^=1;
}

代码

可持久化

非常简单,只要在split和merge时为修改添加节点然后用root数组记录版本就行了
然而。。。

我要疯了。。。
以下是split和merge。

void split(int p,int w,int &x,int &y)
{
    if(p==0)
    {
        x=y=0;
    }
    else
    {
        int newn=++newp;
        nd[newn]=nd[p];
        if(nd[p].w<=w)
        {
            x=newn;
            split(nd[x].r,w,nd[x].r,y);
        }
        else
        {
            y=newn;
            split(nd[y].l,w,x,nd[y].l);
        }
        update(newn);
    }
}  

int merge(int x,int y)
{
    if(x==0||y==0)
    { return x+y; }
    int newn=++newp;
    if(nd[x].rdm>nd[y].rdm)
    {
        nd[newn]=nd[x];
        nd[newn].r=merge(nd[newn].r,y);
    }
    else
    {
        nd[newn]=nd[y];
        nd[newn].l=merge(x,nd[newn].l);
    }
    update(newn);
    return newn;
}

完整代码

原文地址:https://www.cnblogs.com/buringstraw/p/10854340.html

时间: 2024-11-10 08:02:22

震惊!一蒟蒻竟然写出fhqTreap的相关文章

蒟蒻的写题方法的探索

1.先写大体框架,再写小函数与变量 测试题目:https://www.acwing.com/problem/content/174/ 框架如下: #include<bits/stdc++.h> using namespace std; struct rec{ int x,y,lie; }st,ed; void get_st_ed() { for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) { } } void input() { for(int

Charles Petzold 编程界大师,世界顶级技术作家 竟然写出《CODE》如此有耐心的书

<CODE>The Hidden Language of Computer Hardware and Software 从书内容的第一页开始就惊叹于作者的耐心和责任心 整本书以两个隔街对窗住的小朋友在夜晚如何不让大人知道的情况下进行交流以及怎么一步一步的对交流方式改进以提高交流效率开始 从而引出摩尔斯码.接着从物理中最基本的电磁和电路原理出发 讲诉电报如何基于莫尔斯电码发明的 .并联. 串联电路 和反馈电路 如何进行8位数加法 减法 和如何组成寄存器 振荡器 . 寄存器如何组成RAM 以及振荡

蒟蒻ACMer回忆录 &#183; 一段弱校ACM的奋斗史

三年半的ACM生涯终于迎来了终点,退役之时,感慨万分,故写此文以纪念逝去的时光,那些为ACM拼搏的日子,那段弱校ACM的奋斗史. 三年半的ACM生涯,窝见证了CUMT从打铁到铜牌的突破,又见证了从铜牌到银牌的突破:见证了集训队员从3人发展到10余人,又见证了ACM实验室的落实. 三年半的ACM生涯,窝经历了太多,有Accepted时的欢笑,有Wrong Answer时的沮丧,有Time Limit Exceeded时的无奈,有Runtime Error时的尴尬,有打铁的失落,有拿牌的惊喜. 13

算法描述》LCA两三事(蒟蒻向)

LCA是图论中常用的解决树形结构子问题的工具,这一问题一般需要用一个简短的子函数直接解决,但是这对于广大蒟蒻们仍然是一个不小的问题. LCA是指在树形结构中两点的最近公共祖先,对于这个问题,直接向上找事最直接的方法,但同时时间复杂度和数据给出的生成树的层数有关,最优情况是logN级别的,但是如果数据给出的是一条链就GG了,所以要用更优的方法写,一般来说,用的是log2N的操作,最糟糕的复杂度也是logN级别的,那如何实现这一过程捏,我这里有两种方法,和大家分享 第一种:树上倍增 具体方法是对于已

博主自传——蒟蒻的OI之路

博主来自河北石家庄市第二中学,现在读高二,主攻信息学竞赛(其实并没有学习其他学科竞赛). NOIP中人品大爆发,使劲挤进河北省一等奖队伍,侥幸留在竞赛团队中(差点就淘汰出局啦). 关于我的ID,YOUSIKI,就是那四个平假名,没有什么实际意义.若搜到日本某艺人,和我无关. NOIP前主要活跃在PekingUniversityOnlineJudge(不知道拼错木有),现在主要活跃在Lydsy(小伙伴们都叫他BZOJ). 在各个OJ上基本都有号(有大号也有小号),ID都是YOUSIKI.Tyvj,

dp专场的蒟蒻题解

前言:一直想练练dp,正好衣神弄了个训练赛..上次看cgold大佬的题解心血来潮所以自己试着写了第一次题解..可惜本蒟蒻的能力太差有两道题做不太出,只好搬运学习其它大佬的题解了.. a题 https://vjudge.net/contest/355951#problem/A 这题做题的过程十分痛苦 我又双叒叕看错题意了.. 以为是必须在对角线上 其实是随便n*n的都行.. 大概思路是从一个角开始更新,统计左边和上边相同的长度 #include <iostream> #include <c

bzoj 4636: 蒟蒻的数列

4636: 蒟蒻的数列 Description 蒟蒻DCrusher不仅喜欢玩扑克,还喜欢研究数列 题目描述 DCrusher有一个数列,初始值均为0,他进行N次操作,每次将数列[a,b)这个区间中所有比k小的数改为k,他想知 道N次操作后数列中所有元素的和.他还要玩其他游戏,所以这个问题留给你解决. Input 第一行一个整数N,然后有N行,每行三个正整数a.b.k. N<=40000 , a.b.k<=10^9 Output 一个数,数列中所有元素的和 Sample Input 4 2 5

蒟蒻的第一篇博文

先刷一波存在感... 据说维护Blog可以作为积累然后就决定开一波Blog=.= 本来想用Hexo Framework加上GitHub Pages做一个Blog来着(因为GitHub比较Geek嘛) 然后折腾了半天搞了个NexT Theme感觉异常地漂亮 然后Commit上了GitHub还写了几篇测试文章自我感觉良好 然后调整了一发文件夹结构因为我总是要移动工作区 然后折腾了一段时间感觉这个Blog估计可以使了 然后... 然后突然发现Hexo Framework构造的是一大坨静态页面没法加载数

BZOJ3786 星系探索 蒟蒻出题人给跪

本蒟蒻闲得蛋疼于是在BZOJ上加了一道水题,果然被瞬间水过... 只能说本蒟蒻实在是太弱了. Q:你为什么要写这篇博客? A:我只是为了水经验233.... 正常向的数据.题解.标程请自行传送下面的云盘... http://pan.baidu.com/s/1qWsMHM8 吐槽: 为什么本地不开O2 10s在OJ上开O2 还需要20+s啊!!!我本来不想卡常数好不好. 因为这个原因用数组实现数据结构被卡的请见谅...现在是40s应该卡不掉了. 另外如果发现自己被卡掉请重交一次.原因不解释. 为什