zoukankan      html  css  js  c++  java
  • 「学习笔记」 FHQ Treap

    FHQ Treap

    FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不懂,而且它能完成Treap与Splay能完成的所有事,代码短,理解也容易。


    基本操作

    FHQ Treap和Treap很像,都是给每个节点一个随机的权值,使它满足堆的性质。建议先了解Treap(没必要实现,懂得原理即可)。不过,如果有两个节点值相同,FHQ Treap不会用一个数组cnt记录个数,而是直接再开一个节点。

    FHQ的基本操作只有两个:Split与Merge。

    Split表示把一棵树分成两棵,Merge表示把一棵树合并成一棵。


    变量&函数约定

    int L[MAXN], R[MAXN], sz[MAXN], rk[MAXN], val[MAXN], tot;
    int root;
    
    int New( int v ){ return val[++tot] = v, rk[tot] = rand(), L[tot] = R[tot] = 0, sz[tot] = 1, tot; }
    
    #define Updata(x) sz[x] = sz[L[x]] + sz[R[x]] + 1
    

    没写成结构体,没写成指针。

    (L[i])表示(i)的左儿子,(R[i])表示(i)的右儿子,(sz[i])表示以(i)为根的子树包含的节点数,(rk[i])表示为了保持平衡随机赋予的权值,(val[i])表示该节点保存的值,(tot)表示节点数,(root)表示当前的根节点。

    (New(v))表示新建一个值为(v)的节点(可以看成一棵只有一个节点平衡树)

    (Updata(x))表示更新节点(x)(sz)

    提醒:这里“值”与“权值”是不一样的,“值”表示节点保存的值,“权值“仅仅用于维持平衡,注意区分


    Split

    怎么分割呢?

    常见的分割方法有两种,一种是按值分,一种是按排名分(实现差不多,这里只讲按值分)。

    先来看看定义。

    void Split( int c, int k, int &x, int &y );
    

    c表示当前要分割的树的根节点,并且把值(le k)的节点分割出来,构成一棵树,把(x)赋为根节点,其他节点另外构成一棵树,把(y)赋为其根节点。(x)(y)用引用(&)更方便处理。

    对于当前的树,如果根节点(c)的值(le k)(c)的左子树也全部(le k),所以我们可以把(x)赋为(c),保留左子树,将右子树(le k)的部分分割出来作为(x)的右子树。剩下的部分自然也就是在(> k)的部分。(>k)的情况同理。具体我们用递归实现。

    void Split( int c, int k, int &x, int &y ){
    	if ( c == 0 ){ x = y = 0; return; }//如果当前处理的树为空,分出的两个子树当然也为空,所以直接赋值返回。
    	if ( val[c] <= k ) x = c, Split( R[c], k, R[x], y );//如果根节点值小于等于k,把x赋为c,继续处理右子树,并把小于等于k的部分分到x的右子树,其他分到y
    	else y = c, Split( L[c], k, x, L[y] );
    	Updata(c);//别忘了更新sz
    }
    

    Merge

    上面分割的操作不会改变堆的性质与二叉查找树的性质,但是在合并的时候要注意保持堆的性质。

    void Merge( int &c, int x, int y );
    

    表示把以(x)(y)为根节点的树合并,将(c)赋为根节点。

    注意:上面分割时x的所有节点的值都小于y的,合并时也要注意x的所有节点小于等于y,否则会出错

    由于(x)(y)的权值在两颗树中是最大的,所以合并后的树根节点不是(x)就是(y)。所以比较(x)(y)的权值就可以判断谁为根节点。

    假设以(x)为根。因为保证(x)的所有节点的值都小于等于(y)的,所以(y)肯定会合并在(x)的右子树。所以,我们不用动(x)的左子树,合并(x)的右子树与(y)作为(x)的右子树。(y)为根时同理。这样,就巧妙完成了同时维护堆的性质与二叉查找树的性质。

    我们还是用递归。

    void Merge( int &c, int x, int y ){
    	if ( !x || !y ){ c = x | y; return; }
    	if ( rk[x] >= rk[y] ) c = x, Merge( R[x], R[c], y );
    	else c = y, Merge( L[y], x, L[c] );
    	Updata(c);
    }
    

    我刚开始也理解不了这两种操作。主要瓶颈在难以想象。其实可以看做只处理当前的,未处理的留到下一步,反正操作方法都一样。


    剩下的都可以用这两种操作实现。


    插入操作

    直接把它分成(le v)的树和(> v)的树,将新建的节点与(le v)的树合并,再与(>y)树合并即可。

    //opt 1
    void Ins( int v ){
    	int x, y, z(New(v));
    	Split( root, v, x, y );
    	Merge( x, x, z );
    	Merge( root, x, y );
    }
    

    删除操作

    分成(le k)(> k)两颗树,再分成(<k)(=k)(> k)三棵树,将(=k)左右子树合并,相当于删去(=k)的一个节点,然后将三棵树重新合并即可。

    // opt 2
    void Del( int v ){
    	int x, y, z;
    	Split( root, v, x, y );
    	Split( x, v - 1, x, z );
    	Merge( z, L[z], R[z] );
    	Merge( x, x, z );
    	Merge( root, x, y );
    }
    

    查询排名

    其实可以用while循环,,,但是,,,我,,,懒,,,所,,,以,,,直,,,接,,,,,,,

    //opt 3
    int GetRankByVal( int v ){
    	int x, y, t;
    	Split( root, v - 1, x, y );
    	t = sz[x];
    	Merge( root, x, y );
    	return t + 1;
    }
    

    查询值

    这真的不能用Split和Merge偷懒了,,,所以乖乖写个while吧~

    技术含量不高,自行理解。

    //opt 4
    int GetValByRank( int rk ){
    	int c(root);
    	while( c ){
    		if ( sz[L[c]] + 1 == rk ) return val[c];
    		else if ( sz[L[c]] >= rk ) c = L[c];
    		else rk -= 1 + sz[L[c]], c = R[c];
    	}
    	return -1;//题目没要求。。。只是为了自己查错
    }
    

    查询前缀

    分成两颗树(<v)(ge v),在(<v)树中找最大值即可。

    //opt 5
    int GetPre( int v ){
    	int x, y, z;
    	Split( root, v - 1, x, y );
    	z = x;
    	while( R[z] ) z = R[z];
    	Merge( root, x, y );
    	return val[z];
    }
    

    查询后缀

    与查询前缀同理。

    //opt 6
    int GetNxt( int v ){
    	int x, y, z;
    	Split( root, v, x, y );
    	z = y;
    	while( L[z] ) z = L[z];
    	Merge( root, x, y );
    	return val[z];
    }
    

    完整代码

    洛谷P3369 【模板】普通平衡树

    #include<bits/stdc++.h>
    using namespace std;
    #define MAXN 100005
    
    int L[MAXN], R[MAXN], sz[MAXN], rk[MAXN], val[MAXN], tot;
    int root;
    
    int New( int v ){ return val[++tot] = v, rk[tot] = rand(), L[tot] = R[tot] = 0, sz[tot] = 1, tot; }
    #define Updata(x) sz[x] = sz[L[x]] + sz[R[x]] + 1
    
    void Split( int c, int k, int &x, int &y ){
    	if ( c == 0 ){ x = y = 0; return; }
    	if ( val[c] <= k ) x = c, Split( R[c], k, R[x], y );
    	else y = c, Split( L[c], k, x, L[y] );
    	Updata(c);
    }
    
    void Merge( int &c, int x, int y ){
    	if ( !x || !y ){ c = x | y; return; }
    	if ( rk[x] >= rk[y] ) c = x, Merge( R[x], R[c], y );
    	else c = y, Merge( L[y], x, L[c] );
    	Updata(c);
    }
    //opt 1
    void Ins( int v ){
    	int x, y, z(New(v));
    	Split( root, v, x, y );
    	Merge( x, x, z );
    	Merge( root, x, y );
    }
    // opt 2
    void Del( int v ){
    	int x, y, z;
    	Split( root, v, x, y );
    	Split( x, v - 1, x, z );
    	Merge( z, L[z], R[z] );
    	Merge( x, x, z );
    	Merge( root, x, y );
    }
    //opt 3
    int GetRankByVal( int v ){
    	int x, y, t;
    	Split( root, v - 1, x, y );
    	t = sz[x];
    	Merge( root, x, y );
    	return t + 1;
    }
    //opt 4
    int GetValByRank( int rk ){
    	int c(root);
    	while( c ){
    		if ( sz[L[c]] + 1 == rk ) return val[c];
    		else if ( sz[L[c]] >= rk ) c = L[c];
    		else rk -= 1 + sz[L[c]], c = R[c];
    	}
    	return -1;
    }
    //opt 5
    int GetPre( int v ){
    	int x, y, z;
    	Split( root, v - 1, x, y );
    	z = x;
    	while( R[z] ) z = R[z];
    	Merge( root, x, y );
    	return val[z];
    }
    //opt 6
    int GetNxt( int v ){
    	int x, y, z;
    	Split( root, v, x, y );
    	z = y;
    	while( L[z] ) z = L[z];
    	Merge( root, x, y );
    	return val[z];
    }
    
    int T;
    
    int main(){
    	srand(time(0));//随机数种子别忘了
    	root = New(INT_MAX);//虚节点,避免一个节点都没有不方便合并。注意要用一个很大的数,查询排名时就不用-1
    	scanf( "%d", &T );
    	while( T-- ){
    		int opt, x;
    		scanf( "%d%d", &opt, &x );
    		switch( opt ){
    			case 1: Ins(x); break;
    			case 2: Del(x); break;
    			case 3: printf( "%d
    ", GetRankByVal(x) ); break;
    			case 4: printf( "%d
    ", GetValByRank(x) ); break;
    			case 5: printf( "%d
    ", GetPre(x) ); break;
    			case 6: printf( "%d
    ", GetNxt(x) ); break;
    		}
    	}
    	return 0;
    }
    

    FHQ Treap还可以资瓷可持久化~比Treap、Splay好用多啦

  • 相关阅读:
    Array 对象-sort()
    vue安装
    前端面试题
    JavaScript对象原型
    CSS如何水平垂直居中?
    块格式化上下文(Block Formatting Context,BFC)
    盒子模型
    前端基础
    Markdown语法
    浏览器 滚动条 占据 y轴宽度的解决方案
  • 原文地址:https://www.cnblogs.com/louhancheng/p/10238307.html
Copyright © 2011-2022 走看看