首页 > 其他分享 >浅谈 FHQ Treap

浅谈 FHQ Treap

时间:2024-05-19 20:40:36浏览次数:20  
标签:rt 浅谈 merge int tr Treap split 权值 FHQ

浅谈 FHQ Treap

目录

简单介绍

FHQ Treap,以下简写为 \(fhq\),是一种treap(树堆)的变体,功能比treap强大。
\(fhq\) 不需要通过一般平衡树的左右旋转来保持平衡,而是通过分裂 \(split\)和合并 \(merge\) 来实现操作。

优点是比较好理解、代码短、上手快、可持久化

主要是不用像 \(splay\) 一样旋来旋去。

Treap比较好玩的一点是,它整体是拥有二叉搜索树的性质,但是它的每一个节点都会有一个附加权值,它的附加权值是符合堆的性质。

这样我们可以明显看出,这棵树的形态(平衡与否)是由这个附加权值决定的。

那我们该如何取这个权值呢?随机!!

是的,我们每次都随机一个权值作为它的附加权值,那么它就大概是平衡的。

只需要两个基础操作,就可以达到splay的所有功能

前置操作

结构

对于每个树上节点开一个结构体,维护左儿子、右儿子、两个权值、还有子树中的节点个数。

然后给定一个树上的权值 \(v\)​ ,新建节点,还有更新子树大小 \(update\)。

struct fhq {
    int l , r , v , t , sz;
} tr[N];
void update (int x) { tr[x].sz = 1 + tr[tr[x].l].sz + tr[tr[x].r].sz; }
int new_pos (int v) {
    tr[++pos].sz = 1;
    tr[pos].v = v;
    tr[pos].t = rnd ();
    return pos;
}

\(rnd()\) 是随机数生成函数,需要搞一个预处理

std::mt19937 rnd(233);

分裂 split

这个的主要思想就是把一个 \(treap\) 按照一个权值 \(v\) 分成两个。

把所有小于等于 \(v\) 的分到一棵树中,把所有大于 \(v\) 的分到另一棵树中,如下图。

3142227-20240519195920075-1936927548.png (1360×382) (cnblogs.com)

可以对照代码感性理解一下

void split (int now , int k , int &x , int &y) {
    if (now == 0) x = y = 0;
    else {
        if (tr[now].v <= k) 
            x = now , split (tr[now].r , k , tr[now].r , y);
        else 
            y = now , split (tr[now].l , k , x , tr[now].l);
        update (now);
    }
}

合并 merge

\(merge\) 操作按照堆权合并x,y两颗子树,合并前要保证x子树中的权值<y子树中的权值,如下图。

3142227-20240519201419216-492141250.png (1489×368) (cnblogs.com)

图中黄字为堆权

int merge (int x , int y) {
    if (x == 0 || y == 0) return x + y;
    if (tr[x].t < tr[y].t) {
        tr[x].r = merge (tr[x].r , y);
        update (x);
        return x;
    }
    else {
        tr[y].l = merge (x , tr[y].l);
        update (y);
        return y;
    }
}

一般操作

Insert

插入一个权值为 \(v\) 的点,把树按照 \(v\) \(split\) 成两个,再按照顺序合并。

split (rt , a , x , y);
rt = merge (merge (x , new_pos (a)) , y);

delete

删除权值为 \(v\) 的点,需要 \(split\) 两次。一次按照 \(v\) 分成 \(x , z\) ,一次把 \(x\) 按照 \(v - 1\) 分成 \(x , y\) ,那么 \(y\) 的中间点就是 \(v\) ,把 \(y\) 的两个儿子直接合并也就相当于删除了 \(v\) ,然后再按照顺序把其他节点合并就好了。

split (rt , a , x , z);
split (x , a - 1 , x , y);
y = merge (tr[y].l , tr[y].r);
rt = merge (merge (x , y) , z);

查询排名为 \(x\) 的数

就是普通的查询,每次把 \(x\) 和当前节点的左子树大小比较即可。

int kth (int now , int k) {
    while (1) {
        if (k <= tr[tr[now].l].sz) 
            now = tr[now].l;
        else if (k == tr[tr[now].l].sz + 1) 
            return now;
        else 
            k -= tr[tr[now].l].sz + 1 , now = tr[now].r;
    }
}

查询 \(v\) 的排名 rank

把原树 \(split\) 按照 \(v - 1\) 成 \(x , y\) ,\(x\) 的大小 \(+1\) 即为 \(x\) 的排名。

split (rt , a - 1 , x , y);
printf ("%d\n" , tr[x].sz + 1);
rt = merge (x , y);

查询 \(x\) 的前驱 precursor

找前驱的话把原树按 \(v-1\) \(split\)成 \(x,y\),在 \(x\) 里面找最大值。

split (rt , a - 1 , x , y);
printf ("%d\n" , tr[kth (x , tr[x].sz)].v);
rt = merge (x , y);

查询 \(x\) 的后继 successor

跟前驱类似。把原树按 \(v\) \(split\) 成 \(x,y\),在 \(y\) 里找最小值。

split (rt , a , x , y);
printf ("%d\n" , tr[kth (y , 1)].v);
rt = merge (x , y);

版题

题目传送门

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
const int N = 5e5 + 5;
std::mt19937 rnd(233);
int pos , rt;
struct fhq {
    int l , r , v , t , sz;
} tr[N];
void update (int x) { tr[x].sz = 1 + tr[tr[x].l].sz + tr[tr[x].r].sz; }
int new_pos (int v) {
    tr[++pos].sz = 1;
    tr[pos].v = v;
    tr[pos].t = rnd ();
    return pos;
}
void split (int now , int k , int &x , int &y) {
    if (now == 0) x = y = 0;
    else {
        if (tr[now].v <= k) 
            x = now , split (tr[now].r , k , tr[now].r , y);
        else 
            y = now , split (tr[now].l , k , x , tr[now].l);
        update (now);
    }
}
int merge (int x , int y) {
    if (x == 0 || y == 0) return x + y;
    if (tr[x].t < tr[y].t) {
        tr[x].r = merge (tr[x].r , y);
        update (x);
        return x;
    }
    else {
        tr[y].l = merge (x , tr[y].l);
        update (y);
        return y;
    }
}
int kth (int now , int k) {
    while (1) {
        if (k <= tr[tr[now].l].sz) 
            now = tr[now].l;
        else if (k == tr[tr[now].l].sz + 1) 
            return now;
        else 
            k -= tr[tr[now].l].sz + 1 , now = tr[now].r;
    }
}
int main () {
    int T , op , a , x , y , z;
    scanf ("%d" , &T);
    while (T --) {
        scanf ("%d%d" , &op , &a);
        if (op == 1) {
            split (rt , a , x , y);
            rt = merge (merge (x , new_pos (a)) , y);
        }
        else if (op == 2) {
            split (rt , a , x , z);
            split (x , a - 1 , x , y);
            y = merge (tr[y].l , tr[y].r);
            rt = merge (merge (x , y) , z);
        }
        else if (op == 3) {
            split (rt , a - 1 , x , y);
            printf ("%d\n" , tr[x].sz + 1);
            rt = merge (x , y);
        } 
        else if (op == 4) 
            printf ("%d\n" , tr[kth (rt , a)].v);
        else if (op == 5) {
            split (rt , a - 1 , x , y);
            printf ("%d\n" , tr[kth (x , tr[x].sz)].v);
            rt = merge (x , y);
        }  
        else {
            split (rt , a , x , y);
            printf ("%d\n" , tr[kth (y , 1)].v);
            rt = merge (x , y);
        }
    }
    return 0;
}

标签:rt,浅谈,merge,int,tr,Treap,split,权值,FHQ
From: https://www.cnblogs.com/2020fengziyang/p/18200729

相关文章

  • 「网络流浅谈」最小割的模型 1
    最大权闭合子图引入闭合子图指对于子图\(G=(V,E)\),\(\forallu\inV,(u,v)\inE\),都有\(v\inV\)。最大权闭合子图无非就是对于所有的闭合子图\(G\)中\(\sum_{u\inV}w_u\)最大的闭合子图。对于这个图中,闭合子图有哪些呢?红色框圈画出的即为\(1\)个闭合子图,因为......
  • 「网络流浅谈」最大流的应用
    二分图匹配考虑如何将二分图匹配问题,转化为流网络。设置\(1\)个汇点和源点,从源点向二分图一侧的每一个点连边,从另一侧向汇点连边,边权均为\(1\),二分图中的边也全部加入,权值设为\(1\)。这样,二分图的最大匹配等于流网络的最大流。P2756飞行员配对方案问题题意:给定\(1\)个二......
  • 浅谈Vue.js与原生开发
    在现代的Web开发中,前端框架的选择是至关重要的。Vue.js作为一款流行的前端框架,与传统的原生开发相比,有许多明显的区别。模版语法与HTMLVue.js使用特殊的模板语法来创建动态视图,这样开发者可以更方便地表达复杂的逻辑。通过指令(例如v-if、v-for等)和模板表达式,Vue.js简化了数据的......
  • PMP备考必看 | 浅谈PMP证书的价值 | PMP考试详细全流程
    为什么拥有PMP证书的人会被优先考虑呢?需要考试资料的朋友可以加我V.X:huangwanwei99或者QQ:869255552讲实话,PMP证书目前在我国许多项目的招标过程中是非常有必要的。一些大型企业、跨国公司、事业单位、央企和国企等在招标文件中要求明确提及必须有PMP持有者参与。这至少表示在......
  • 浅谈AI智能分析与视频流媒体能力下的自然灾害预防监测应用
    一、方案背景夏季是灾害多发季节,山洪、泥石流、洪涝、冰雹、飓风、地震等自然灾害每年都给国家经济带来巨大的损失。随着人工智能(AI)和视频流媒体技术的发展,这些技术在自然灾害预防和监测方面的应用正在变得越来越广泛和重要。AI智能分析技术能够处理复杂的数据,并且能够帮助人们......
  • 浅谈基于uinapp制作一个搞笑图片生成器
    制作一个搞笑图片生成器可以使用多种技术,其中UniApp是一个不错的选择,因为它允许开发者使用一套代码开发多平台应用。以下是使用UniApp制作搞笑图片生成器的基本步骤:1.项目规划在开始编码之前,你需要规划你的应用。确定你的搞笑图片生成器将包含哪些功能,例如:图片选择:允许用户从......
  • fhq-treap
    一些细节本质是利用合并、分裂实现增、删、查。根据用途分为两类分裂:第一类:当作set一样使用,就是中序遍历就把数字排序了。分裂操作按照权值分裂。如果根\(\lek\),那么左边都要归入\(x\),递归右边,\(x\)换成右边(看还能接上去多少)\(>k\)同理,最后pushup一下。第二......
  • 论术:浅谈防御性编程
    WHAT在防御式驾驶中拥有这样一种思维,那就是你永远也不能确定另一位老司机将要做什么。为了防止在其他人做出危险动作时你也不会受到伤害,你要承担起保护自己的责任,哪怕是其他司机犯的错误,这就是所谓防御性编程的意义所在。防御性编程是一种细致、谨慎的编程方法。为了开发可靠的......
  • FHQ Treap
    P3369【模板】普通平衡树前言:平衡树是一种二叉搜索树,通过一些方法来做到快速维护单点或区间信息和快速查询单点或区间信息,其中包括排名、前驱等等。在\(\rmSTL\)库中虽有实现,但是由于封装的太好以及是可持久化数据结构的基础,还是需要学习的。FHQTreap:FHQTreap是一种不......
  • 浅谈质量方案--制定产品质量目标
    最近看来不少相关的内容,测试架构,测试策略,测试方案等等,今天一起来浅谈一下~首先要做测试方案(质量方案),第一步就是定标准,质量目标(质量标准,测试目标,测试标准),定这个的目的是测试是无穷的,但测试人员和时间是有限的,我们不可能穷尽所有盲目混乱测试,所以需要我们的目标是什么,达到对应的质......