一.前言
这个数据结构早有耳闻.只是一直没学.跟着AgOH学了一波.发现很简单.
二.原理
二叉搜索树之所以不够优秀,是因为存在一些数据能够将二叉搜索树退化成一条链.从而导致平方级的复杂度.那么对于这个缺陷,大佬们想出了很多方法去解决,衍生出了①替罪羊树②有旋/无旋Treap③splay.今天我入门学习的就是无旋treap.
Treap之所以能够克服二叉搜索树的缺陷,主要因为它以随机的方式构造二叉搜索树.使得毒瘤数据失效.具体就是将二叉堆和二叉搜索树结合起来.而无旋treap就不需要旋转操作.直接split+merge搞就可以了过程非常像玩拼图.
split有两种方式:按值分裂和按大小分裂.
1.将树分裂成两个部分,左部符合所有值小于等于val,右部符合所有值大于val.
2.将树分裂成两个部分,左部符合子树大小恰好等于val,右部符合大小为补集.
merge的过程很像线段树合并操作.
三.具体实现
说实话搞懂split操作和merge操作,基本就搞懂fhq-treap了.
大概结构:
1.每个节点需要存储:
struct Node
{
// 左右儿子,该点的值,该点的优先级,该子树大小.
int l , r , val , key , sz;
}fhq[maxn];
2.新建节点操作:
int newnode (int val){
fhq[++cnt].val = val;
fhq[cnt].key = rnd(); // 复杂度的保证
fhq[cnt].sz = 1;
return cnt;
}
3.在split/merge后更新节点信息:
void pushup (int x){
fhq[x].sz = fhq[fhq[x].l].sz + fhq[fhq[x].r].sz + 1;
}
4.分裂操作:
//注:当前节点在now,以 val为界限分成两部分.左边的父亲节点是x.右边的父亲节点是y.
void split (int now , int val , int &x , int &y)
{
if (!now){
x = y = 0;
return ;
}
// 代表根和左子树属于集合x.右子树尚未确定,需要继续抽离.
if (fhq[now].val <= val){
x = now;
split(fhq[now].r , val , fhq[now].r , y);
}else {
// 反之一样.
y = now;
split(fhq[now].l , val , x , fhq[now].l);
}
pushup(now);
}
5.合并操作:
//注:将子树x和子树y合并,返回根节点.
// 有个前提条件:子树x的值全严格小于子树y的值,这样才能保证它在合并后还是颗二叉搜索树.
int mer (int x , int y)
{
// 任意一个子树为空,返回另一个.两个都为空,返回空.
if (!x || !y) return x + y;
// 它同时又是一颗二叉堆,满足优先级特点.
if (fhq[x].key > fhq[y].key){
// x既要作为根,又因为前提,所以y要在x的右下方.
fhq[x].r = mer(fhq[x].r , y);
pushup(x);
return x;
}
// 反之一样.
fhq[y].l = mer(x , fhq[y].l);
pushup(y);
return y;
}
四.模板题目
4.1.洛谷P3369:普通平衡树
fhqTreap可以实现所有其他平衡树能实现的效果.
1.插入数
2.删除 x 数(若有多个相同的数,因只删除一个)
3.查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
4.查询排名为 x 的数
5.求 x 的前驱(前驱定义为小于 x,且最大的数)
6.求 x 的后继(后继定义为大于 x,且最小的数)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
struct Node
{
int l , r , val , key , sz;
}fhq[maxn];
int cnt , rt;
mt19937 rnd(233);
int newnode (int val){
fhq[++cnt].val = val;
fhq[cnt].key = rnd();
fhq[cnt].sz = 1;
return cnt;
}
void pushup (int x){
fhq[x].sz = fhq[fhq[x].l].sz + fhq[fhq[x].r].sz + 1;
}
void split (int now , int val , int &x , int &y)
{
if (!now){
x = y = 0;
return ;
}
if (fhq[now].val <= val){
x = now;
split(fhq[now].r , val , fhq[now].r , y);
}else {
y = now;
split(fhq[now].l , val , x , fhq[now].l);
}
pushup(now);
}
int mer (int x , int y)
{
if (!x || !y) return x + y;
if (fhq[x].key > fhq[y].key){
fhq[x].r = mer(fhq[x].r , y);
pushup(x);
return x;
}
fhq[y].l = mer(x , fhq[y].l);
pushup(y);
return y;
}
int x , y , z;
void ins (int val)
{
split(rt , val - 1 , x , y);
rt = mer(mer(x , newnode(val)) , y);
}
void del (int val)
{
split(rt , val - 1, x , z);
split(z , val , y , z);
y = mer(fhq[y].l , fhq[y].r);
rt = mer(mer(x , y) , z);
}
int getrk (int val)
{
split(rt , val - 1 , x , y);
int res = fhq[x].sz + 1;
rt = mer(x , y);
return res;
}
int getval (int rk)
{
int now = rt;
while (now){
if (fhq[fhq[now].l].sz == rk - 1) break;
if (fhq[fhq[now].l].sz >= rk) now = fhq[now].l;
else rk -= fhq[fhq[now].l].sz + 1 , now = fhq[now].r;
}
return fhq[now].val;
}
int pre (int val)
{
split(rt , val - 1 , x , y);
int now = x;
while (fhq[now].r) now = fhq[now].r;
rt = mer(x , y);
return fhq[now].val;
}
int aft (int val)
{
split(rt , val , x , y);
int now = y;
while (fhq[now].l) now = fhq[now].l;
rt = mer(x , y);
return fhq[now].val;
}
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
for (int i = 1 ; i <= n ; i++){
int op , x; cin >> op >> x;
if (op == 1){
ins(x);
}else if (op == 2){
del(x);
}else if (op == 3){
cout << getrk(x) << endl;
}else if (op == 4){
cout << getval(x) << endl;
}else if (op == 5){
cout << pre(x) << endl;
}else if (op == 6){
cout << aft(x) << endl;
}
}
return 0;
}
4.2.洛谷P3391 文艺平衡树
题目地址
通过这道题目,我们发现fhqTreap同样可以维护区间序列信息.这个时候就是把下标当作节点大小.按子树大小分裂.然后对于任何区间的操作都是将这段区间的子树扣出来进行操作再合并回去.具体看代码.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
struct Node
{
int l , r , val , key , sz;
bool re;
}fhq[maxn];
int cnt , rt;
mt19937 rnd(233);
int newnode (int val)
{
fhq[++cnt].val = val;
fhq[cnt].sz = 1;
fhq[cnt].key = rnd();
return cnt;
}
void pushup (int x){
fhq[x].sz = fhq[fhq[x].l].sz + fhq[fhq[x].r].sz + 1;
}
void pushdown (int x){
if (!fhq[x].re) return ;
swap(fhq[x].l , fhq[x].r);
fhq[x].re = 0;
fhq[fhq[x].l].re ^= 1;
fhq[fhq[x].r].re ^= 1;
return ;
}
// 将now这个子树按sz分成两个部分,sz部分接在x,大于部分接在y
void split (int now , int sz , int &x , int &y)
{
if (!now) {
x = y = 0;
return ;
}
pushdown(now);
if (fhq[fhq[now].l].sz < sz){
x = now;
split(fhq[now].r , sz - fhq[fhq[now].l].sz - 1 , fhq[now].r , y);
}else {
y = now;
split(fhq[now].l , sz , x , fhq[now].l);
}
pushup(now);
}
int mer (int x , int y)
{
if (!x || !y) return x + y;
if (fhq[x].key > fhq[y].key){
pushdown(x);
fhq[x].r = mer(fhq[x].r , y);
pushup(x);
return x;
}
pushdown(y);
fhq[y].l = mer(x , fhq[y].l);
pushup(y);
return y;
}
int x , y , z;
void re (int l , int r)
{
split(rt , l - 1 , x , z);
split(z , r - l + 1 , y , z);
fhq[y].re ^= 1;
rt = mer(mer(x , y) , z);
}
void dfs (int now)
{
if (!now) return ;
pushdown(now);
dfs(fhq[now].l);
cout << fhq[now].val << " ";
dfs(fhq[now].r);
return ;
}
int main()
{
ios::sync_with_stdio(false);
int n , m; cin >> n >> m;
for (int i = 1 ; i <= n ; i++){
rt = mer(rt , newnode(i));
}
for (int i = 1 ; i <= m ; i++){
int l , r; cin >> l >> r;
re(l , r);
}
dfs(rt);
return 0;
}
4.3.P3372 线段树 1
类似4.2的做法.每个节点记录一个add标记和sum代表子树的val和.注意pushdown的时候和线段树类似.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define fi first
#define se second
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
struct Node
{
int l , r , key , sz , add;
ll val , sum;
}fhq[maxn];
int cnt , rt;
mt19937 rnd(233);
int newnode (ll val)
{
fhq[++cnt].val = val;
fhq[cnt].sum = val;
fhq[cnt].sz = 1;
fhq[cnt].key = rnd();
return cnt;
}
void pushup (int x){
fhq[x].sz = fhq[fhq[x].l].sz + fhq[fhq[x].r].sz + 1;
fhq[x].sum = fhq[fhq[x].l].sum + fhq[fhq[x].r].sum + fhq[x].val;
}
void D (int x , ll c)
{
fhq[x].add += c;
fhq[x].sum += fhq[x].sz * c;
}
void pushdown (int x){
if (!fhq[x].add) return ;
fhq[x].val += fhq[x].add;
D (fhq[x].l , fhq[x].add);
D (fhq[x].r , fhq[x].add);
fhq[x].add = 0;
return ;
}
// 将now这个子树按sz分成两个部分,sz部分接在x,大于部分接在y
void split (int now , int sz , int &x , int &y)
{
if (!now) {
x = y = 0;
return ;
}
pushdown(now);
if (fhq[fhq[now].l].sz < sz){
x = now;
split(fhq[now].r , sz - fhq[fhq[now].l].sz - 1 , fhq[now].r , y);
}else {
y = now;
split(fhq[now].l , sz , x , fhq[now].l);
}
pushup(now);
}
int mer (int x , int y)
{
if (!x || !y) return x + y;
if (fhq[x].key > fhq[y].key){
pushdown(x);
fhq[x].r = mer(fhq[x].r , y);
pushup(x);
return x;
}
pushdown(y);
fhq[y].l = mer(x , fhq[y].l);
pushup(y);
return y;
}
int x , y , z;
void add (int l , int r , ll v)
{
split(rt , l - 1 , x , z);
split(z , r - l + 1 , y , z);
fhq[y].add += v;
rt = mer(mer(x , y) , z);
}
ll ask (int l , int r)
{
split(rt , l - 1 , x , z);
split(z , r - l + 1 , y , z);
ll res = fhq[y].sum;
rt =mer(mer(x , y) , z);
return res;
}
int main()
{
ios::sync_with_stdio(false);
int n , m; cin >> n >> m;
for (int i = 1 ; i <= n ; i++){
int x; cin >> x;
rt = mer(rt , newnode(x));
}
for (int i = 1 ; i <= m ; i++){
int op , x , y , k; cin >> op;
if (op == 1){
cin >> x >> y >> k;
add(x , y , k);
}else {
cin >> x >> y;
cout << ask(x , y) << endl;
}
}
return 0;
}