fhq treap

fhq-treap 小结

粗浅地学习了这个神奇的数据结构,下面瞎写一些感受

首先fhq treap是一个基于分裂与合并的平衡树,那么怎么分裂,怎么合并呢

我们分两种情况考虑

一、权值平衡树(我自己取的名字)

所谓权值平衡树,就是任何操作都只与权值有关的平衡树

比如最基础的分裂,合并操作

分裂就是把平衡树按照权值\(k\)分成两半,一边所有点的权值\(\leq k\),另一边权值\(\gt k\)

怎么分裂呢

首先根据\(treap\)的定义,所有点的权值是一颗二叉搜索树(BST),就是左边比他小,右边比他大

那么分裂的时候,我们比较当前节点和\(k\)的大小,如果比\(k\)大,那么显然右子树内全部节点都比\(k\)大

所以我们把当前节点加上他的右子树连到第二棵树上,剩下的左子树继续分裂

如果比\(k\)小也是一样的

Example Code:
void split(int &x,int &y,int k,int nw){
   //x表示第一棵子树上的某个节点的某个指针,y是第二棵子树上的,nw是当前节点
   if(nw==0){x=y=0;return;}
   if(tr[x].val>k){
      y=nw;split(x,tr[nw].l,k,tr[nw].l);
   }
   else{
      x=nw;split(tr[nw].r,y,k,tr[nw].r);
   }
   update(nw);
}

然后合并就更简单了

就是把两颗子树(默认其中有一棵每个点的权值都小于另一棵任何点)合成一个

因为有上面那个条件,所以只考虑那个随机值的大小使得树满足堆的性质即可

那么比较当前两棵树中的两个当前节点:

如果第一棵树的rand值小,那么这个点连到总树上,然后这个点的左子树肯定不受到第二棵子树的影响(权值小于第二棵子树),那么递归合并这个点的右子树和第二棵子树即可

反之亦然

Example Code:
int merge(int x,int y){
   if(!x || !y) return x+y; //精妙的写法,避免一堆if
   if(tr[x].rnd<tr[y].rnd){
      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;
   }
}

这两种操作都是\(O(深度)\)的,所以复杂度是\(\log\)级别的

那么其他的操作就是两个操作拼凑起来

比如插入一个数,就是先分裂成小于等于这个数的一部分,大于这个数的一部分,然后当前数当做第三部分,做\(merge(merge(l,nw),r)\)即可

比如删除一个数,就是先分裂成小于等于这个数的一部分,大于这个数的一部分,然后再把第一棵树分成等于这个数和小于这个数,然后在等于这个数的树里把根删掉(merge根的两个子树),最后全部合起来就可以了

再比如求排名,可以分出小于这个数的一部分,这个部分的size就是排名了

等等。。。

但是有一个问题,就是常数比较大

比如删除操作常数可能到5~6,比起什么线段树,树状数组之类的慢多了

但是在平衡树里面算快的了。。。(垃圾数据结构)

但是这样怎么处理区间赋值,区间加这种操作呢?没有办法。。。

因为你在处理的时候一直保持有序状态,顺序就乱套了,没法取出区间

所以我们引入第二类fhq-treap

二、序列操作平衡树(仍旧是我自己取的名字。。。)

这个平衡树的时间复杂度很奇怪,反正我觉得不是\(\log\)的,但是好像跑的挺快

有没有人会证复杂度的请留言,谢谢

这个和上面那个的唯一区别就是他不满足BST的性质(或者满足但是我没有发现?)

首先我们观察他的build函数(就是把一个无序数组\(O(n)\)建成treap)

int build(int *data,int k){
    int nw=0,pre=0;top=0;
    rep(i,1,k){
        nw=newnode(data[i]);pre=0;
        while(top && rnd[sta[top]]>rnd[nw]){
            update(sta[top]);
            pre=sta[top];
            sta[top--]=0;
        }
        if(top) son[sta[top]][1]=nw;
        son[nw][0]=pre;sta[++top]=nw;
    }
    while(top) update(sta[top--]);
    return sta[1];
}

其中data就是这个数组,k是他的长度

sta维护的是一个栈,top是栈顶指针

newnode(x)是新建一个权值为x的节点并且返回下标

这是什么意思呢

就是说我们只关心随机值,保持他是个堆

那么我们顺着插入每一个元素

在栈里存储最右链,栈顶存储最底下的元素,栈底存储根,那么rand值显然从栈顶到栈底递减,然后我们在栈里找,直到找到第一个rand小于当前rand的元素,那么这个元素就是当前元素的父亲

然后我们假装数组是有序的,那么新加进去的元素肯定最大,所以他就是他父亲的右儿子

他父亲原来的右子树变成他的左子树(权值比他小)

然后更新最右链

从中我们显然发现不满足BST的性质(或者说下标满足BST的性质,但是我们存储的值是权值而非下标,也就是说下标并没记录,在操作几次之后就全乱了),那么复杂度是怎么回事啊。。。

先不说复杂度的事了,我们先看有哪些操作

最基础的依旧是split和merge

void split(int &x,int &y,int k,int nw){
    if(nw==0){x=y=0;return;}
    pushdown(nw);
    if(sz[son[nw][0]]>=k){
        y=nw;split(x,son[nw][0],k,son[nw][0]);
    }
    else{
        x=nw;split(son[nw][1],y,k-sz[son[nw][0]]-1,son[nw][1]);
    }
    update(nw);
}

int merge(int x,int y){
    if(x) pushdown(x);if(y) pushdown(y);
    if(!x || !y) return x+y;
    if(rnd[x]<rnd[y]){
        son[x][1]=merge(son[x][1],y);
        update(x);return x;
    }
    else{
        son[y][0]=merge(x,son[y][0]);
        update(y);return y;
    }
}

我们看跟之前的区别

实际上区别不太大,就是把split中val的比较变成了子树大小的比较,传进去的k指的是位置而非权值

然后注意到多了一个pushdown函数

什么鬼。。。这不是线段树操作吗。。。

好的,为了处理区间操作,我们引入了懒标记(好强啊),因为我们的treap不再按val分而是按下标分,所以一个节点的儿子也是他的子区间,和线段树类似,所以我们可以完成懒标记的下放操作

void pushdown(int x){
    if(son[x][0]) change(son[x][0],lazy[x]);
    if(son[x][1]) change(son[x][1],lazy[x]);
    lazy[x]=0;
}

事实上现在我们就讲完了所有基础操作了,可以做很多题了,就是把这些操作拼拼凑凑即可

下面我们看一道例题:

bzoj1500 维修数列

请写一个程序,要求维护一个数列,支持以下 6 种操作:

请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格

我们一个个操作分析

首先插入操作

他并不插入一个数,他插入一堆数。。。

怎么办呢

首先插入总次数是有限制的,所以每次暴力插入应该是可行的,(不过因为复杂度本身就很奇怪所以t了也是正常的事)

但是我们有更好的插入方法

插入一个的时候,我们是merge三棵树,现在也一样嘛,就是先把这个序列建成一个treap,然后一起merge起来

因为他插入位置是连续的一段,所以merge是正确的

然后删除操作

就是拆成三颗子树,然后把中间的扔掉

然后修改

记录标记表示这个区间被修改成几了,如果为inf则没被修改

那么只要拆成三颗树,然后把根打一下标记就好了

翻转

先拆成三棵树

然后把中间的树每一层都翻转

这样就炸了

所以我们打上一个翻转标记,翻转一次就xor 1

求和

sum是很好维护的,因为修改的时候只要记录size就可以维护了,翻转的时候总的sum不变

求最大子序列

这个有点麻烦

维护三个量,首先lmx表示这个节点对应区间的左边一整段的最大和,rmx就是右边一整段的最大和,tmx表示经过这个节点的根的最大和

然后tmx[root]就是答案

更新详见代码

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
    ll 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;
}

const int inf=1000000000;
const int maxn=1000100;
queue<int> trash;int n,m,a[maxn];
int sta[maxn],son[maxn][2],sz[maxn],sum[maxn],val[maxn],tmx[maxn],lmx[maxn],rmx[maxn];
int cnt,lazy_revise[maxn],lazy_reverse[maxn],rnd[maxn],top,aa[maxn],root;

void update(int x){
    int l=son[x][0],r=son[x][1];
    if(l && r){
        sz[x]=sz[l]+sz[r]+1;
        sum[x]=sum[l]+sum[r]+val[x];
        tmx[x]=max(tmx[l],tmx[r]);
        tmx[x]=max(tmx[x],rmx[l]+val[x]+lmx[r]);
        lmx[x]=max(lmx[l],sum[l]+val[x]+lmx[r]);
        rmx[x]=max(rmx[r],rmx[l]+val[x]+sum[r]);
    }
    else if(l){
        sz[x]=sz[l]+1;
        sum[x]=sum[l]+val[x];
        tmx[x]=max(tmx[l],rmx[l]+val[x]);
        lmx[x]=max(max(lmx[l],sum[l]+val[x]),0);
        rmx[x]=max(0,val[x]+rmx[l]);
    }
    else if(r){
        sz[x]=sz[r]+1;
        sum[x]=sum[r]+val[x];
        tmx[x]=max(tmx[r],lmx[r]+val[x]);
        rmx[x]=max(rmx[r],sum[r]+val[x]);
        rmx[x]=max(0,rmx[x]);
        lmx[x]=max(0,lmx[r]+val[x]);
    }
    else{
        sz[x]=1;
        sum[x]=tmx[x]=val[x];
        lmx[x]=rmx[x]=max(val[x],0);
    }
}

int newnode(int k){
    int x=0;
    if(!trash.empty()){
        x=trash.front();trash.pop();
    }
    else x=++cnt;
    son[x][0]=son[x][1]=lazy_reverse[x]=0;
    lazy_revise[x]=inf;
    rnd[x]=rand();sz[x]=1;val[x]=sum[x]=tmx[x]=k;
    lmx[x]=rmx[x]=max(k,0);return x;
}

int build(int *data,int k){
    int nw=0,pre=0;top=0;
    rep(i,1,k){
        nw=newnode(data[i]);pre=0;
        while(top && rnd[sta[top]]>rnd[nw]){
            update(sta[top]);
            pre=sta[top];
            sta[top--]=0;
        }
        if(top) son[sta[top]][1]=nw;
        son[nw][0]=pre;sta[++top]=nw;
    }
    while(top) update(sta[top--]);
    return sta[1];
}

void doit(int x){
    if(x==0) return;
    trash.push(x);
    doit(son[x][0]),doit(son[x][1]);
}

void change(int x,int k){
    val[x]=k;sum[x]=sz[x]*k;
    lmx[x]=rmx[x]=max(sum[x],0);
    tmx[x]=max(sum[x],val[x]);
    lazy_revise[x]=k;
}

void flip(int x){
    swap(son[x][0],son[x][1]);
    swap(lmx[x],rmx[x]);
    lazy_reverse[x]^=1;
}

void pushdown(int x){
    if(lazy_revise[x]!=inf){
        if(son[x][0]) change(son[x][0],lazy_revise[x]);
        if(son[x][1]) change(son[x][1],lazy_revise[x]);
    }
    if(lazy_reverse[x]!=0){
        if(son[x][0]) flip(son[x][0]);
        if(son[x][1]) flip(son[x][1]);
    }
    lazy_revise[x]=inf;lazy_reverse[x]=0;
}

void split(int &x,int &y,int k,int nw){
    if(nw==0){x=y=0;return;}
    pushdown(nw);
    if(sz[son[nw][0]]>=k){
        y=nw;split(x,son[nw][0],k,son[nw][0]);
    }
    else{
        x=nw;split(son[nw][1],y,k-sz[son[nw][0]]-1,son[nw][1]);
    }
    update(nw);
}

int merge(int x,int y){
    if(x) pushdown(x);if(y) pushdown(y);
    if(!x || !y) return x+y;
    if(rnd[x]<rnd[y]){
        son[x][1]=merge(son[x][1],y);
        update(x);return x;
    }
    else{
        son[y][0]=merge(x,son[y][0]);
        update(y);return y;
    }
}

void ins(){
    int pos=read(),len=read();
    rep(i,1,len) aa[i]=read();
    int nw=build(aa,len);
    int x,y;
    split(x,y,pos,root);
    root=merge(merge(x,nw),y);
}

void del(){
    int pos=read(),len=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    root=merge(x,w);
    doit(z);
}

void upd(){
    int pos=read(),len=read(),k=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    change(z,k);root=merge(x,merge(z,w));
}

void rev(){
    int pos=read(),len=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    flip(z);root=merge(x,merge(z,w));
}

void calc1(){
    int pos=read();
    int len=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    printf("%d\n",sum[z]);
    root=merge(x,merge(z,w));
}

void calc2(){
    printf("%d\n",tmx[root]);
}

int main(){
    srand(20020709);
    n=read();m=read();
    rep(i,1,n) a[i]=read();
    root=build(a,n);
    while(m--){
        string s;cin>>s;
        if(s[0]==‘I‘) ins();
        else if(s[0]==‘D‘) del();
        else if(s[0]==‘M‘ && s[2]==‘K‘) upd();
        else if(s[0]==‘R‘) rev();
        else if(s[0]==‘G‘) calc1();
        else calc2();
    }
    return 0;
}

原文地址:https://www.cnblogs.com/wawawa8/p/9595209.html

时间: 2024-09-29 08:48:41

fhq treap的相关文章

【bzoj1251】序列终结者——fhq treap

Description 给定一个长度为N的序列,每个序列的元素是一个整数.要支持以下三种操作: 1. 将[L,R]这个区间内的所有数加上V. 2. 将[L,R]这个区间翻转,比如1 2 3 4变成4 3 2 1. 3. 求[L,R]这个区间中的最大值. 最开始所有元素都是0. Input 第一行两个整数N,M.M为操作个数. 以下M行,每行最多四个整数,依次为K,L,R,V.K表示是第几种操作,如果不是第1种操作则K后面只有两个数. Output 对于每个第3种操作,给出正确的回答. Sampl

【POJ2761】【fhq treap】A Simple Problem with Integers

Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval. In

FHQ Treap小结(神级数据结构!)

首先说一下, 这个东西可以搞一切bst,treap,splay所能搞的东西 pre 今天心血来潮, 想搞一搞平衡树, 先百度了一下平衡树,发现正宗的平衡树写法应该是在二叉查找树的基础上加什么左左左右右左右右的旋转之类的, 思路比较好理解,但是 代码量........ 一看就头大,, 然后,在洛谷翻题解的时候无意间看到了远航之曲发的一篇非常短小精悍的题解, 于是就学了一下 FHQ Treap 这个东西的学名应该是叫做fhq treap,应该是treap的强化版. 整个数据结构中只有两个操作: 1.

浅谈fhq treap

一.简介 fhq treap 与一般的treap主要有3点不同 1.不用旋转 2.以merge和split为核心操作,通过它们的组合实现平衡树的所有操作 3.可以可持久化 二.核心操作 代码中val表示节点权值,pri表示节点的优先级,维护小根堆 1.split 将1个treap分裂为两个treap 分裂主要有两种:以权值k作为分界点.以位置k作为分界点 ①以权值k作为分界点 设原来的treap根节点为root,分裂后的<=k的treap A 的根节点为x,>k的treap B 的根节点为y

平衡树合集(Treap,Splay,替罪羊,FHQ Treap)

今天翻了翻其他大佬的博客,发现自己有些...颓废... 有必要洗心革面,好好学习 序:正常的BST有可能退化,成为链,大大降低效率,所以有很多方法来保持左右size的平衡,本文将简单介绍Treap,Splay,替罪羊,FHQ Treap: 另:代码都是普通平衡树 1.Treap 树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树.其基本操作的期望时间复杂度为O(logn).相对于其他的平衡二叉搜索树,Treap的特点是实现简单,

P2710 数列[fhq treap]

调了一辈子的fhq treap- 如果不会最大子段和 如果不会fhq treap 7个操作- 其中三个查询 单点查询其实可以和区间查询写成一个( fhq treap 的修改操作大概就是 \(split\) 完了然后把修改区间的根 打上标记 等着下传就完事了- 那这题没了-我给个好一点的小数据-反正我照着这个调了挺久的- .in 50 10 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 3

可持久化Treap(fhq Treap,非旋转式Treap)学习(未完待续)

简介: Treap,一种表现优异的BST 优势: 其较于AVL.红黑树实现简单,浅显易懂 较于Splay常数小,通常用于树套BST表现远远优于Splay 或许有人想说SBT,SBT我没有实现过,据说比较快 但是SBT.Splay以及旋转版Treap等BST都不可以比较方便地实现‘可持久化操作 Treap=Tree+Heap Treap是一颗同时拥有二叉搜索树和堆性质的一颗二叉树 Treap有两个关键字,在这里定义为: 1.key,满足二叉搜索树性质,即中序遍历按照key值有序 2.fix,满足堆

洛谷P3391 【模板】文艺平衡树(Splay)(FHQ Treap)

题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

模板 - 数据结构 - 无旋Treap / FHQ Treap

普通平衡树: #include<bits/stdc++.h> using namespace std; typedef long long ll; #define ls(p) ch[p][0] #define rs(p) ch[p][1] const int MAXN = 100000 + 5; int val[MAXN], ch[MAXN][2], rnd[MAXN], siz[MAXN], tot, root; void Init() { tot = root = 0; } void Pu