左偏树 / 非旋转treap学习笔记
背景
非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap.
左偏树
定义
- 左偏树(Leftist Tree)是一种可并堆(Mergeable Heap), 它除了支持优先队列的三个基本操作(插入,删除,取最小节点), 还支持一个很特殊的操作——合并操作;
- 左偏树是一棵堆有序(Heap Ordered)二叉树;
- 左偏树满足左偏性质(Leftist Property):
- 节点的键值小于或等于它的左右子节点的键值;
- 节点的左子节点的距离不小于右子节点的距离;
- 节点的左子节点右子节点也是一颗左偏树.
操作
注: 这里的左偏树默认是大根堆.
合并
合并两棵左偏树. 在合并操作中, 假设这两棵treap的根节点分别为u和v.
- 比较这两个根节点的权值, 大的作为当前节点,
- 合并当前节点的右儿子和另一个根节点.
- 比较当前节点的两个儿子, 假如右儿子的距离大于左儿子, 则交换两个儿子节点.
插入
插入一个节点.
把这个节点当成是只有根节点的一棵左偏树, 用合并的方法合并至原树中即可.
时间复杂度及证明
我们发现, 对于一棵点数为\(n\)的左偏树, 其最右链的长度不超过\(\log n\)(这里的证明我不太会, 就稍微意会一下吧), 因此合并两棵大小为\(n\)的左偏树的时间复杂度为\(O(\log n)\). 我们考虑开始时每棵左偏树都只有一个节点, 则从初始状态合并至所有节点都在一棵树上的时间复杂度为$$O\left(\frac{n}{2^1} \log 2^0 + \frac{n}{2^2} \log 2^1 + \frac{n}{2^3} \log 2^2 + ... + \frac{n}{2^{k + 1}} \log 2^k \right) = O\left(\frac{n}{2} \log n\right) = O(n \log n)$$
代码
直接看Monkey King的代码就可以了吧.
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <algorithm>
const int N = (int)1e5;
namespace Zeonfai
{
inline int getInt()
{
int a = 0, sgn = 1;
char c;
while(! isdigit(c = getchar()))
if(c == '-')
sgn *= -1;
else if(c == EOF)
exit(0);
while(isdigit(c))
a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
void _print(int a)
{
if(! a)
return;
_print(a / 10);
putchar('0' + a % 10);
}
inline void println(int a)
{
if(a < 0)
putchar('-'), a *= -a;
if(! a)
putchar(0);
_print(a);
putchar('\n');
}
}
struct disjointSet
{
int anc[N + 1];
inline void initialize()
{
memset(anc, -1, sizeof(anc));
}
int access(int u)
{
return ~ anc[u] ? anc[u] = access(anc[u]) : u;
}
void link(int u, int pre)
{
anc[u] = pre;
}
}st;
struct leftistTrees
{
struct node
{
int suc[2], dis, w;
}nd[N + 1];
inline void newNode(int u, int w)
{
nd[u].w = w, nd[u].dis = 0, nd[u].suc[0] = nd[u].suc[1] = -1;
}
inline void modify(int u)
{
newNode(u, nd[u].w >> 1);
}
inline int merge(int pre, int u, int v)
{
if(! (~ u))
{
if(~ v)
st.link(v, pre);
return v;
}
if(! (~ v))
{
if(~ u)
st.link(u, pre);
return u;
}
if(nd[u].w < nd[v].w)
std::swap(u, v);
nd[u].suc[1] = merge(u, nd[u].suc[1], v);
if(! (~ nd[u].suc[0]) || nd[nd[u].suc[1]].dis > nd[nd[u].suc[0]].dis)
std::swap(nd[u].suc[0], nd[u].suc[1]);
nd[u].dis = ~ nd[u].suc[1] ? nd[nd[u].suc[1]].dis + 1 : 0;
st.link(u, pre);
return u;
}
inline int pop(int u)
{
return merge(-1, nd[u].suc[0], nd[u].suc[1]);
}
inline int get(int u)
{
return nd[u].w;
}
}hp;
int main()
{
#ifndef ONLINE_JUDGE
freopen("HDU1512.in", "r", stdin);
freopen("HDU1512.out", "w", stdout);
#endif
using namespace Zeonfai;
while(int n = getInt())
{
for(int i = 1; i <= n; ++ i)
hp.newNode(i, getInt());
int m = getInt();
st.initialize();
for(int i = 0; i < m; ++ i)
{
int u = getInt(), v = getInt(), rtU = st.access(u), rtV = st.access(v);
if(rtU == rtV)
{
puts("-1");
continue;
}
int nwRtU = hp.pop(rtU), nwRtV = hp.pop(rtV);
hp.modify(rtU), hp.modify(rtV);
rtU = hp.merge(-1, nwRtU, rtU), rtV = hp.merge(-1, nwRtV, rtV);
int rt = hp.merge(-1, rtU, rtV);
println(hp.get(rt));
}
}
}
非旋转treap
定义
treap即是tree + heap. 不懂的可以先学习普通treap.
非旋转treap的特别之处在于, 它的操作不依赖于旋转, 而使用类似于左偏树的合并的方式来实现.
操作
首先是一些基本的操作:
合并
给定两棵treap的根节点, 将它们合并起来. 注意这里要求其中一棵treap的任意元素都比另一棵的任意元素小. 实现: 首先把优先级高的一个根节点作为当前节点, 假如另一棵treap比当前点小, 则将其与当前点的左儿子合并, 否则和右儿子合并. 是不是和左偏树的合并非常类似?
分裂
对于一棵以u为根的treap, 将其权值前\(k\)小与剩下部分切开成 两棵 treap并且将其根节点返回. 令sz为根节点节点的左子节点的大小分三种情况讨论:
- \(sz = k\), 则直接断开左儿子, 返回左儿子和当前节点即可;
- \(sz > k\), 同样地断开左儿子, 递归左子树, 得到的结果假设为\((first, second)\), 合并\(u\)和\(second\)作为第二关键字返回即可.
- \(sz < k\), 断开右儿子, 递归右子树, 得到的结果假设为\((first, second)\), 将\(u\)与\(first\)合并作为第一关键字返回即可.
余下的操作都是基于以上的merge和split.
插入
直接merge上去即可.
删除 / 查询
假设要删除第\(L\)到第\(R\)个点, 则先split(R), 再对返回的\(first\)进行\(split(L)\)得到的second即是\([L, R]\)区间.
时间复杂度
显然的\(O(n \log n)\)
代码
只写了merge和split, 而且正确性无法保证.
#include <cstdio>
#include <cctype>
struct treap
{
struct node
{
int w, fix, sz;
node* suc[2];
};
node* rt;
node* merge(node *u, node *v)
{
if(u == NULL)
return v;
if(v == NULL)
return u;
if(u->fix < v->fix)
std::swap(u, v);
u->suc[v->w > u->w] = merge(u->suc[v->w > u->w], v);
return u;
}
std::pair<node*, node*> split(node *u, int k)
{
int tmp = u->suc[0] == NULL ? 0 : u->suc[0]->sz;
if(tmp == k)
{
u->suc[0] = NULL;
return std::make_pair(u->suc[0], u);
}
else if(tmp > k)
{
std::pair<node*, node*> res = split(u->suc[0], k);
u->suc[0] = NULL;
return std::make_pair(res.first(), merge(res.second, u));
}
else if(tmp < k)
{
std::pair<node*, node*> res = split(u->suc[1], k - tmp - 1);
u->suc[1] = NULL;
return std::make_pair(merge(res.first, u), res.second);
}
}
}
int main()
{
using namespace Zeonfai;
int n = getInt();
for(int i = 0; i < n; ++ i)
{
int opt = getInt();
if(opt == 1)
}
}
就这样吧.
左偏树 / 非旋转treap学习笔记的更多相关文章
- Treap + 无旋转Treap 学习笔记
普通的Treap模板 今天自己实现成功 /* * @Author: chenkexing * @Date: 2019-08-02 20:30:39 * @Last Modified by: chenk ...
- 【loj2568】【APIO2016】【学习笔记 左偏树】烟花表演
题目 一棵树,\(n\)个非叶子节点,编号为\(1-n\),\(m\)个叶子节点,编号为\(n+1-n+m\) 每条边有边权,修改边权的代价为\(|a-b|\) ; 定义一个叶子的距离为到1(根节点) ...
- 平衡树及笛卡尔树讲解(旋转treap,非旋转treap,splay,替罪羊树及可持久化)
在刷了许多道平衡树的题之后,对平衡树有了较为深入的理解,在这里和大家分享一下,希望对大家学习平衡树能有帮助. 平衡树有好多种,比如treap,splay,红黑树,STL中的set.在这里只介绍几种常用 ...
- 斜堆,非旋转treap,替罪羊树
一.斜堆 斜堆是一种可以合并的堆 节点信息: struct Node { int v; Node *ch[]; }; 主要利用merge函数 Node *merge(Node *x, Node *y) ...
- 关于非旋转treap的学习
非旋转treap的操作基于split和merge操作,其余操作和普通平衡树一样,复杂度保证方式与旋转treap差不多,都是基于一个随机的参数,这样构出的树树高为\(logn\) split 作用:将原 ...
- [bzoj3196][Tyvj1730]二逼平衡树_树套树_位置线段树套非旋转Treap/树状数组套主席树/权值线段树套位置线段树
二逼平衡树 bzoj-3196 Tyvj-1730 题目大意:请写出一个维护序列的数据结构支持:查询给定权值排名:查询区间k小值:单点修改:查询区间内定值前驱:查询区间内定值后继. 注释:$1\le ...
- 【bzoj3224】Tyvj 1728 普通平衡树 01Trie姿势+平衡树的四种姿势 :splay,旋转Treap,非旋转Treap,替罪羊树
直接上代码 正所谓 人傻自带大常数 平衡树的几种姿势: AVL Red&Black_Tree 码量爆炸,不常用:SBT 出于各种原因,不常用. 常用: Treap 旋转 基于旋转操作和随机数 ...
- [NOIP]2017列队——旋转treap/非旋转treap
Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia所在的方阵中有n × m名学生,方阵的行数为 n,列数为m. 为了便 ...
- [bzoj3173]最长上升子序列_非旋转Treap
最长上升子序列 bzoj-3173 题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度. 注释:1<=n<=10,000,开始序列为 ...
随机推荐
- SQL登录注册练习
/class User package com.neusoft.bean; public class User { private int password; private String name; ...
- 学习pwn的一些指导
使用ret2libc攻击方法绕过数据执行保护 http://blog.csdn.net/linyt/article/details/43643499 格式化字符串利用小结 http://www.cnb ...
- Linux学习-RPM 软件管理程序: rpm
RPM 默认安装的路径 一般来说,RPM 类型的文件在安装的时候,会先去读取文件内记载的设定参数内容,然后将该数据用来比对 Linux 系统的环境,以找出是否有属性相依的软件尚未安装的问题. 若环境检 ...
- 光学字符识别OCR-7语言模型
由于图像质量等原因,性能再好的识别模型,都会有识别错误的可能性,为了减少识别错误率,可以将识别问题跟统计语言模型结合起来,通过动态规划的方法给出最优的识别结果.这是改进OCR识别效果的重要方法之一. ...
- MySQL中的DDL(Data Definition Language,数据定义语言)
create(创建表) 标准的建表语句: create table [模式名.]表名 ( #可以有多个列定义 columnName1 dataType [default expr(这是默认值)], . ...
- Leetcode 436.寻找右区间
寻找右区间 给定一组区间,对于每一个区间 i,检查是否存在一个区间 j,它的起始点大于或等于区间 i 的终点,这可以称为 j 在 i 的"右侧". 对于任何区间,你需要存储的满足条 ...
- Unity3D - UGUI的手动搭建
了解UGUI组件的搭建方式,有助于搭建我们自己的UI界面. Text 文本 text 是UGUI中的基本控件,在Hierarchyi面板创建一个空物体 - 给这个空物体添加一个Text组件即可实现与t ...
- Lyft Level 5 Challenge 2018 - Final Round (Open Div. 2) A. The King's Race
http://codeforces.com/contest/1075/problem/A On a chessboard with a width of nn and a height of nn, ...
- HUST-1407 郁闷的小J
离线做法:分别处理每个编号上的各种询问和操作,接着就能用树状数组维护. #include <cstdlib> #include <cstdio> #include <cs ...
- POJ3349 Snowflake Snow Snowflakes 【哈希表】
题目 很简单,给一堆6元组,可以从任意位置开始往任意方向读,问有没有两个相同的6元组 题解 hash表入门题 先把一个六元组的积 + 和取模作为hash值,然后查表即可 期望\(O(n)\) #inc ...