首页 > 其他分享 >FHQ-treap 学习笔记

FHQ-treap 学习笔记

时间:2022-09-28 02:33:06浏览次数:91  
标签:int siz void 笔记 treap maxn split inline FHQ

介绍

平衡树

平衡树,又称treap,是树(tree)以及堆(heap)的合称,具体表现为形式上它是一棵二叉树,而性质上它又满足堆的性质。与普通的 BST(Binary Search Tree) 相比,它由于具有了堆的性质从而使二叉树平衡,避免了当数据单调时BST退化成链的劣势,将时间复杂度降到了均摊 \(O(\log n)\) 的级别

FHQ-treap

FHQ-treap是一种无旋平衡树,拥有码量小、容易写的优点。具体通过 \(split\) 和 \(merge\) 两种操作实现,即将整棵树分裂为两棵树的操作与将两棵树合并为一棵完整的平衡树的,对节点进行操作时视情况将整棵平衡树按权值或按排名分裂,从而避免了普通treap里面繁琐的旋转操作。

核心操作

upd

功能

很简单,更新以 \(p\) 为根节点的树的大小信息,这个信息在与排名有关的操作中很有用

code

inline void upd(int p) {siz[p] = siz[lc[p]] + siz[rc[p]] + 1;}

split

功能

将根节点为 \(p\) 的树分裂为根节点为 \(l\) 和 \(r\) 的两棵 treap,并且使树 \(l\) 上的所有权值都小于等于 \(v\),树 \(r\) 上的所有权值都大于 \(v\), 这两棵树仍然满足 \(treap\) 的性质。

code

void split(int p, int v, int &l, int &r) {
    if(!p) {l = r = 0; return;}
    if(val[p] <= v) l = p, split(rc[p], v, rc[p], r);
    else r = p, split(lc[p], v, l, lc[p]);
    upd(p);
}

理解

如图,如果从根也就是24按38分裂的话,调用的代码如下

split(rt, 38, x, y); 

因为 \(38 > 24\) ,所以会将 \(24\) 暂时赋值给 \(x\),但是由于BST的性质,\(39\) 的左儿子小于 \(39\),也可能小于 \(v\) ,在这个例子中也就是 \(33\),所以将 \(rc_p\) 作为参数 \(l\) 传进下一层递归中,因为 \(l\) 和 \(r\) 是引用的,所以可以将 \(39\) 的左儿子赋值给 \(rc_p\) ,这样的话就能使分裂后的两棵树满足BST的性质。

这里给的是按权值分裂的代码,如果要实现按排名分裂,就需要判断 \(siz_{lc_p}\) 与排名 \(k\) 的关系了


merge

功能

可以理解为 split 的逆运算,将根节点为 \(l\) 和 \(r\) 的两棵树合并并且根据 \(l\) 和 \(r\) 的随机权值将这棵树的根节点赋为 \(l\) 或 \(r\)

code

inline int merge(int l, int r) {
	if(!l || !r) return l + r;
	if(rd[l] <= rd[r]) {
		lc[r] = merge(l, lc[r]);
		upd(r); return r;
	}
	else {
		rc[l] = merge(rc[l], r);
		upd(l); return l;
	}
}

理解

我们在记忆或者说理解 splitmerge 的函数操作时只需要谨记一点:BST的性质,即左子树上的权值永远小于根节点,右子树上的权值永远大于根节点

merge 这个函数的操作我就不单独说了


ins

code

inline void rand(int &xx) {xx = (rnd = (rnd * 73819) % 23333);}
inline void cre(int v) {
	rand(rd[++ cnt]);
	val[cnt] = v;
	siz[cnt] = 1;
}
inline void ins(int v) {
	split(rt, v, x, y);
	cre(v);
	rt = merge(merge(x, cnt), y);
}

理解

cre 以及 rand 没啥好说的,新建一个节点,节点总数 \(+1\) ,给这个节点赋一个随机值,更新权值以及这个节点的大小。

需要注意 cre 的功能仅仅为新建一个节点的信息,执行完以后这个节点独立于整棵树之外,在 insert 函数中才会将它插入到整棵树当中


例题

P3369 【模板】普通平衡树

Description

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

1.插入 \(x\) 数

2.删除 \(x\) 数(若有多个相同的数,因只删除一个)

3.查询 \(x\) 数的排名(排名定义为比当前数小的数的个数 \(+1\) )

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

5.求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)

6.求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)

Solution

平衡树板题,关于排名的一切操作都用到 \(siz\) 就好了

Code

#include<iostream>
#include<stdio.h>
#define maxn 100005
using namespace std;
int lc[maxn], rc[maxn], siz[maxn], val[maxn], rd[maxn];
int rnd = 1978347, x, y, z, rt, cnt;
inline void rand(int &xx) {xx = (rnd = (rnd * 73819) % 23333);}
inline void cre(int v) {
	rand(rd[++ cnt]);
	val[cnt] = v;
	siz[cnt] = 1;
}
inline void upd(int p) {siz[p] = siz[lc[p]] + siz[rc[p]] + 1;}
inline void split(int p, int v, int &l, int &r) {
	if(!p) {l = r = 0; return;}
	if(val[p] <= v) l = p, split(rc[p], v, rc[p], r);
	else r = p, split(lc[p], v, l, lc[p]);
	upd(p);
}
inline int merge(int l, int r) {
	if(!l || !r) return l + r;
	if(rd[l] <= rd[r]) {
		lc[r] = merge(l, lc[r]);
		upd(r); return r;
	}
	else {
		rc[l] = merge(rc[l], r);
		upd(l); return l;
	}
}
inline void ins(int v) {
	split(rt, v, x, y);
	cre(v);
	rt = merge(merge(x, cnt), y);
}
inline void del(int v) {
	split(rt, v, x, y);
	split(x, v - 1, x, z);
	z = merge(lc[z], rc[z]);
	rt = merge(merge(x, z), y);
}
inline int findrank(int v) {
	split(rt, v - 1, x, y);
	int tmp = siz[x] + 1;
	rt = merge(x, y);
	return tmp;
}
inline int kth(int k) {
	int p = rt;
	while(p) {
		if(siz[lc[p]] + 1 == k) break;
		else if(siz[lc[p]] + 1 >= k) p = lc[p];
		else k -= siz[lc[p]] + 1, p = rc[p]; 
	}
	return val[p];
}
inline int front(int v){
	split(rt, v - 1, x, y);
	int p = x;
	while(rc[p]) p = rc[p];
	rt = merge(x, y);
	return val[p];
}
inline int back(int v){
	split(rt, v, x, y);
	int p = y;
	while(lc[p]) p = lc[p];
	rt = merge(x, y);
	return val[p];
}
int main(){
	int n, opt, xx;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
		scanf("%d%d", &opt, &xx);
		if(opt == 1) ins(xx);
		else if(opt == 2) del(xx);
		else if(opt == 3) printf("%d\n", findrank(xx));
		else if(opt == 4) printf("%d\n", kth(xx));
		else if(opt == 5) printf("%d\n", front(xx));
		else printf("%d\n", back(xx));
	}
	return 0;
} 


P1503 鬼子进村

Description

有 \(n\) 个房子,排成一排,第 \(i\) 个房子只与第 \(i - 1\) 和 \(i + 1\) 个相连,下面有 \(m\) 个信息:

1.若消息为 D x:鬼子将 \(x\) 号房子摧毁了,地道被堵上。

2.若消息为 R :村民们将鬼子上一个摧毁的房子修复了。

3.若消息为 Q x:有一名士兵被围堵在 \(x\) 号房子中,输出他能到达的房子数量。

Solution

1.若消息为 D x,将 \(x\) 插入平衡树。

2.若消息为 R ,将上一个 \(x\) 删除。

3.若消息为 Q x,根据 \(x\) 的前驱和后继求能到达的房子树

注意,在开始将 \(0\) 和 \(n + 1\) 就插入平衡树

Code

#include<iostream>
#include<cstdio>
#include<stack>
#define maxn 50004
using namespace std;
int lc[maxn], rc[maxn], val[maxn], siz[maxn], rd[maxn], rnd = 97231, cnt, rt, x, y, z;
bool des[maxn];
inline void rand(int &x) {x = (rnd = (rnd * 233531) % 1000000);}
inline void cre(int v) {
	rand(rd[++ cnt]);
	val[cnt] = v;
	siz[cnt] = 1;
}
inline void upd(int p) {siz[p] = siz[lc[p]] + siz[rc[p]] + 1;}
inline void split(int p, int v, int &l, int &r) {
	if(!p) {l = r = 0; return;}
	if(v >= val[p]) l = p, split(rc[p], v, rc[p], r); 
	else r = p, split(lc[p], v, l, lc[p]);
	upd(p);
}
inline int merge(int l, int r) {
	if(!l || !r) return l + r;
	if(rd[l] <= rd[r]) {
		lc[r] = merge(l, lc[r]); 
		upd(r); return r;
	}
	else {
		rc[l] = merge(rc[l], r);
		upd(l); return l;
	}
}
inline void ins(int v) {
	split(rt, v, x, y);
	cre(v);
	rt = merge(merge(x, cnt), y);
}
inline void del(int v) {
	split(rt, v - 1, x , y);
	split(y, v, y, z);
	y = merge(lc[y], rc[y]);
	rt = merge(merge(x, y), z);
}
inline int front(int v) {
	split(rt, v - 1, x, y);
	int p = x;
	while(rc[p]) p = rc[p];
	rt = merge(x, y);
	return val[p];
}
inline int back(int v) {
	split(rt, v, x, y);
	int p = y;
//	cout << lc[p] << "n\n";
	while(lc[p]) p = lc[p];
	rt = merge(x, y);
	return val[p];
}
stack<int> st;
int n, m, q;
char tmp;
int main() {
	ios :: sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	ins(0);
	ins(n + 1);
	for(int i = 1; i <= m; ++ i) {
		cin >> tmp;
		if(tmp == 'D') cin >> q, ins(q), st.push(q), des[q] = 1;
		else if(tmp == 'Q'){
			cin >> q; 
			if(des[q]) printf("0\n"); 
			else printf("%d\n", back(q) - front(q) - 1);
		}
		else del(st.top()), des[st.top()] = 0, st.pop();
	}
	return 0;
}

标签:int,siz,void,笔记,treap,maxn,split,inline,FHQ
From: https://www.cnblogs.com/8atemak1r/p/16736609.html

相关文章

  • axios学习笔记
     一.  安装json-server 01安装npminstall-gjson-serverhttps://github.com/typicode/json-server 02,新建一个db.json文件,把上面链接文档的数据放上去......
  • CSS基础(基于黑马程序员视频的学习笔记)
    一、CSS选择器1、标签选择器选中所有的该标签标签名{CSS属性名:属性值;}2、类选择器.类名{CSS属性名:属性值;}所有标签都有class属性,class属性的属......
  • 九月第二篇关于《程序员修炼之道:从小工到专家》的阅读笔记
    《程序员修炼之道:从小工到专家》阅读笔记这本书是自从进入软件工程系以来所阅读的第二本书,本篇是九月的第二篇阅读笔记,希望在这里记录一些我的感悟。书中中间几个章节提......
  • 前端三件套 HTML+CSS+JS基础知识内容笔记
    HTML基础目录HTML基础HTML5标签doctype标签html标签head标签meta标签title标签body标签文本和超链接标签标题标签段落标签换行标签水平标签强调标签图片标签与超链接标签......
  • 读书笔记|择一城以定财富,择一行以定发展
    题记“最近个人财政吃紧,想着病急乱投医”,看看理财区有什么好的书籍,然后就用两天时间读完了这本——《钱从哪里来》。作者:香帅,本名唐涯,知名金融学者,香帅数字金融工作室创始人......
  • 学习笔记:python列表2
    python学习列表的进一步运用1.减少元素(1)delplace=['lasa','chengdu','litang','xian','lundon']delplace[0]#输出['chengdu','litang','xian','lundon'......
  • 大话设计模式 ---- 第一章简单工厂笔记
    第一章简单工厂模式计算器实现建民哥在大二的时候让我们设计一个口算卡我第一版的设计模式:(虽然功能实现了,但是啥也不是,一旦有新要求需要大改程序直接作废)//......
  • 学习笔记:python:字典删除问题
    python学习:字典学习问题:如何删除字典中的一类元素题目:删除字典friends中年龄大于23的friend一个个删除明显达不到考察的目的,所以刚开始我的想法是:利用循环遍历字典中的......
  • Flask学习笔记(六)-蓝图 blueprint的基本使用
    一、前言蓝图(blueprint)技术,可以帮助你实现flask应用的模块划分,在组织flask代码时,有两种模式,分别为功能式架构和分区式架构,使用蓝图,可以让项目架构更有层次,模块划分更便......
  • Linux驱动|rtc-hym8563移植笔记
    本文基于瑞芯微rk3568平台,关于该平台快速入手操作,大家可以参考以下文章:《瑞芯微rk356x板子快速上手》0、什么是rtc-hym8563?RTC:实时时钟的缩写是(Real_TimeClock)。RTC......