fhqtreap初探
介绍
fhqtreap为利用分裂和合并来满足平衡树的性质,不需要旋转操作的一种平衡树。
并且利用函数式编程可以极大的简化代码量。
(题目是抄唐神的来着)
核心操作
(均为按位置分裂合并)
struct fhq {
int lc, rc, siz, rnd, val;
//lc为左子树,rc为右子树,siz为子树大小(位置分裂即按siz分裂),rnd为随机值,val为该节点储存的值
}t[N];
#define lc (t[rt].lc)
#define rc (t[rt].rc)
//下方用到的宏定义
split(rt,l,r,k) 把一个根为rt的子树split成一个根为l和一个根为r的子树(以第k大为界限)
void split(int rt, int &l, int &r, int k) {
if(!k) l = 0, r = rt;
else if(t[rt].siz == k) l = rt, r = 0;
else if(k <= t[lc].siz) r = rt, split(lc, l, lc, k), up(rt);
else l = rt, split(rc, rc, r, k - t[lc].siz - 1), up(rt);
}
merge(rt,l,r) 把根为l和根为r的子树merge成一个根为rt的子树
merge默认子树l的权值比子树r的权值小
merge满足小根堆性质(对rnd)
void merge(int &rt, int l, int r) {
if(!l || !r) rt = l + r;
else if(t[l].rnd < t[r].rnd) rt = l, merge(rc, rc, r), up(rt);
else rt = r, merge(lc, l, lc), up(rt);
}
fhqtreap也有一种按权值分裂的做法,但用处不大,如果要用位置分裂实现权值分裂,可以将序列构造成一个递增的序列,写一个rank求一下插入的数的在序列中的位置,插入到那里就行了(这样就能搞权值分裂能搞的东西了)、
区间操作打懒标记的话,在split和merge的时候下传即可。
常用操作
注意,当需要求max或者min的时候,一定要把root的max/min和val都设成inf/-inf
里面的sum即为上方提到的val。
建新节点
int build(int val) {
t[++tot].rnd = rand() << 15 | rand();
t[tot].siz = 1;
t[tot].sum = val;
return tot;
}
查排名(上方提到的rank,这个是\(logn\)的)
int rank(int rt, int val) {
if(!rt) return 0;
if(t[rt].sum == val) return t[lc].siz + 1;
if(t[rt].sum > val) return rank(lc, val);
return rank(rc, val) + t[lc].siz + 1;
}
插入
inline void insert(int val) {
int rk = rank(root, val), x, y;
split(root, x, y, rk);
int z = build(val);
merge(x, x, z); merge(root, x, y);
}
删除
inline void del(int val) {
int rk = rank(root, val) + 1, x, y, z;
split(root, x, y, rk);
split(x, x, z, rk - 1);
merge(root, x, y);
}
其他操作均类似。
要注意的一个问题:merge和split均针对的是根为rt的子树,所以对应的k也是他们子树中的第k大。可以看看下面的代码。
inline int find(int rk) {//找排名为rk的数
int x, y, z, ans;
split(root, x, y, rk - 1);
split(y, y, z, 1);
ans = t[y].sum;
merge(y, y, z); merge(root, x, y);
return ans;
/*
split(root, x, y, rk); split(x, z, x, rk - 1);
ans = t[x].sum;
merge(x, z, x), merge(root, x, y);
return ans;
*/
}
即在一整个根为root的树中找第rk大,可以有两种实现:1.分裂成前rk大和剩下的,并将前rk大分裂成rk-1大和第rk大。2.分裂成前rk-1大和剩下的,将剩下的那部分,分裂成第一大(不要弄成rk+1!)和后面的
模板题
LuoguP3369 【模板】普通平衡树
就是平衡树的常见操作,可以直接写权值分裂,也可以写位置分裂(构造成递增数列)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <queue>
#include <ctime>
#include <cmath>
#include <stack>
#include <deque>
#include <map>
#include <set>
#define ll long long
const int inf = 2e9 + 10;
typedef unsigned long long ull;
namespace io {
#define in(a) a = read()
#define out(a) write(a)
#define outn(a) out(a), putchar('\n')
#define I_int int
inline I_int read() {
I_int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
char F[200];
inline void write(I_int x) {
if (x == 0) return (void) (putchar('0'));
I_int tmp = x > 0 ? x : -x;
if (x < 0) putchar('-');
int cnt = 0;
while (tmp > 0) {
F[cnt++] = tmp % 10 + '0';
tmp /= 10;
}
while (cnt > 0) putchar(F[--cnt]);
}
#undef I_int
}
using namespace io;
using namespace std;
#define N 500010
#define lc (t[rt].l)
#define rc (t[rt].r)
struct fhq {
int l, r, sum, siz, rnd;
} t[N];
int tot = 0, root = 0;
void up(int rt) {
if(!rt) return;
t[rt].siz = 1 + t[lc].siz + t[rc].siz;
}
void split(int rt, int &l, int &r, int k) {
//把根为rt的子树,以第k个为界限split成两个子树
//第k个可以是位置,也可以是权值
//这里的k是位置
if(!k) l = 0, r = rt;
else if(k == t[rt].siz) l = rt, r = 0;
else if(k <= t[lc].siz) r = rt, split(lc, l, lc, k), up(rt);
else l = rt, split(rc, rc, r, k - t[lc].siz - 1), up(rt);
}
void merge(int &rt, int l, int r) {
//把l子树和r子树merge为一棵根为rt的子树
if(!l || !r) rt = l + r;
else if(t[l].rnd < t[r].rnd) rt = l, merge(t[rt].r, t[rt].r, r), up(rt);
else rt = r, merge(t[rt].l, l, t[rt].l), up(rt);
}
int build(int val) {
t[++tot].rnd = rand() << 15 | rand();
t[tot].siz = 1;
t[tot].sum = val;
return tot;
}
int rank(int rt, int val) {
if(!rt) return 0;
if(t[rt].sum >= val) return rank(lc, val);
return rank(rc, val) + t[lc].siz + 1;
}
inline void insert(int val) {
int rk = rank(root, val), x, y;
split(root, x, y, rk);
int z = build(val);
merge(x, x, z); merge(root, x, y);
}
inline void del(int val) {
int rk = rank(root, val) + 1, x, y, z;
split(root, x, y, rk);
split(x, x, z, rk - 1);
merge(root, x, y);
}
inline int find(int rk) {
int x, y, z, ans;
split(root, x, y, rk - 1);
split(y, y, z, 1);
ans = t[y].sum;
merge(y, y, z); merge(root, x, y);
return ans;
/*
split(root, x, y, rk); split(x, z, x, rk - 1);
ans = t[x].sum;
merge(x, z, x), merge(root, x, y);
return ans;
*/
}
inline int pre(int val) {
int x, y, z, ans, rk = rank(root, val);
split(root, x, y, rk);
split(x, z, x, rk - 1);
ans = t[x].sum;
merge(x, z, x); merge(root, x, y);
return ans;
}
inline int succ(int val) {
int x, y, z, ans, rk = rank(root, val + 1);
split(root, x, y, rk + 1); split(x, z, x, rk);
ans = t[x].sum;
merge(x, z, x); merge(root, x, y);
return ans;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
#endif
srand((unsigned)time(0));
t[0].rnd = t[0].sum = inf;
int n = read();
for(int i = 1; i <= n; ++i) {
int op = read(), x = read();
if(op == 1) insert(x);
if(op == 2) del(x);
if(op == 3) printf("%d\n", rank(root, x) + 1);
if(op == 4) printf("%d\n", find(x));
if(op == 5) printf("%d\n", pre(x));
if(op == 6) printf("%d\n", succ(x));
}
}
例题
LuoguP2161 [SHOI2009]会场预约
BZOJ1861: [Zjoi2006]Book 书架
BZOJ1251: 序列终结者
POJ3580 SuperMemo
fhqtreap初探的更多相关文章
- 【算法】fhqtreap初探
NOIP回来就一直想着学平衡树...平衡树写久了调不出来真的会头脑发热.jpg 大概只写了几道题... fhqtreap是不需要旋转的平衡树,仅使用分裂合并,一样可以保持平衡树的性质,并且可以非常简单 ...
- 初探领域驱动设计(2)Repository在DDD中的应用
概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...
- CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探
CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...
- 从273二手车的M站点初探js模块化编程
前言 这几天在看273M站点时被他们的页面交互方式所吸引,他们的首页是采用三次加载+分页的方式.也就说分为大分页和小分页两种交互.大分页就是通过分页按钮来操作,小分页是通过下拉(向下滑动)时异步加载数 ...
- JavaScript学习(一) —— 环境搭建与JavaScript初探
1.开发环境搭建 本系列教程的开发工具,我们采用HBuilder. 可以去网上下载最新的版本,然后解压一下就能直接用了.学习JavaScript,环境搭建是非常简单的,或者说,只要你有一个浏览器,一个 ...
- .NET文件并发与RabbitMQ(初探RabbitMQ)
本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址:http://www.cnblogs.com/tdws/p/5860668.html 想必MQ这两个字母对于各位前辈们和老司 ...
- React Native初探
前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...
- 【手把手教你全文检索】Apache Lucene初探
PS: 苦学一周全文检索,由原来的搜索小白,到初次涉猎,感觉每门技术都博大精深,其中精髓亦是不可一日而语.那小博猪就简单介绍一下这一周的学习历程,仅供各位程序猿们参考,这其中不涉及任何私密话题,因此也 ...
- Key/Value之王Memcached初探:三、Memcached解决Session的分布式存储场景的应用
一.高可用的Session服务器场景简介 1.1 应用服务器的无状态特性 应用层服务器(这里一般指Web服务器)处理网站应用的业务逻辑,应用的一个最显著的特点是:应用的无状态性. PS:提到无状态特性 ...
随机推荐
- 用Hexo在GitHub上搭建个人博客
我用Hexo在GitHub上搭建好了自己的博客,我的这第一篇博客就来说说搭建的过程. 1 环境配置 本文使用环境如下: Windows 10 node.js v8.1.3 git v2.13.2 np ...
- Mvcpager以下各节已定义,但尚未为布局页“~/Views/Shared/_Layout.cshtml”呈现:“Scripts”。
解决办法如下: 1.在_Layout.cshtml布局body内,添加section,Scripts.Render和RenderSection标签示例代码如下: <body class=&quo ...
- 20155228 2016-2017-2 《Java程序设计》第3周学习总结
20155228 2016-2017-2 <Java程序设计>第3周学习总结 教材学习内容总结 认识对象 类与对象 类和对象的关系:类是对象的设计图,对象是类的实例 参考:将"名 ...
- npm 包下载的各种姿势
最近在写Node程序的时候,突然对 npm install 的-save和-save-dev 这两个参数的使用比较混乱.其实博主在这之前对这两个参数的理解也是模糊的,各种查资料和实践后对它们之间的异同 ...
- c# 静态方法和数据
c#所有方法都必须在类的内部声明,但如果把方法或者字段声明为static就可以使用,类名代用方法或者访问字段. 在方法中声明一个静态变量a 和一个静态的aFun方法.下面是在主函数中调用. 从上图可以 ...
- centos 6.5 防火墙开放指定端口
清除防火墙规则:iptables -F 关闭防火墙 /etc/init.d/iptables stop 关闭防火墙开机自启:chkconfig iptables off 查看iptables 是否开 ...
- AtCoder Regular Contest 077 D - 11
题目链接:http://arc077.contest.atcoder.jp/tasks/arc077_b Time limit : 2sec / Memory limit : 256MB Score ...
- 听 Fabien Potencier 谈Symfony2 之 《What is Symfony2 ?》
Symfoy2 是什么? PHP世界里又一广受关注的web MVC框架? Fabien Potencier 却不这么说! Fabien Potencier这样定义Symfoy2 是个什么东西: 首先, ...
- Navicat连接MySQL8.0亲测有效
今天下了个 MySQL8.0,发现Navicat连接不上,总是报错1251: 原因是MySQL8.0版本的加密方式和MySQL5.0的不一样,连接会报错. 试了很多种方法,终于找到一种可以实现的: 更 ...
- 获取select被选中的option的值
<select id="select"> <option>绥江</option> <option>西江</ ...