fhq平衡树学习笔记

前言

因为不会平衡树所以经常被吊打 然后我只会set去搞这个东西但是有些题就不打行

但是Treap,Splay看上去就很难 好像也没多少时间学了 (大佬都吊打我)

于是转去学了fhq平衡树 发现这个东西确实好写

fhq平衡树是什么?其实就是无旋treap

什么 你不知道treap是什么? 那堆和二叉搜索树应该知道吧 其实就是HEAP加上一个BST

而Treap的形态是由一个随机的附加权值来决定的 但是旋转这个东西又难写又难调(数据结构带师无视我的话)

那么fhq平衡树到底是什么 如何实现

它只需要split(分裂)和merge(合并) 操作就能基本实现splay的所有功能

split

主要就是把一棵treap分成两棵 有两种写法 一种按照权值来 一种按照排名(前k个分配)来

下面是按权值来的写法

void split(int now,int k,int &x,int &y)
{
    if(!now) x = y = 0;
    else
    {
    	if(val[now] <= k)
    	   x = now, split(ch[now][1], k, ch[now][1], y);
    	else
    	   y = now, split(ch[now][0], k, x, ch[now][0]);
    	pushup(now);
	}
}

代码是什么意思捏 首先我们把now为根节点的treap分成x和y两棵

满足的是 左边的树权值都是小于等于k的 右边的权值都是大于k的

那么我们遍历到一个点的时候 检查它是大于k还是小于等于k 如果小于等于k 我们直接把它的左子树都分到左边的树x里,但是它的右子树我们还不知道该怎么分

于是我们接着遍历右子树看看它是哪些部分分到左边的树x里, 哪些部分分到右边的树y里 如果到达叶子结点了直接返回即可

最坏情况下一直分到底 效率就是 \(log (n)\)

merge

我们现在要把x和y进行合并 具体如何合并捏

把两个treap合并成一个 我们要保证第一个的权值小于第二个 由于第一个treap的权值已经都小于第二个treap了 所以为了维护我们新的treap的形态

我们需要用附加权值来平衡(维护小根堆)

但是别忘了同样BST的性质也不能丢 具体而言

如果x的根的附加权值小于y的根的附加权值 那么好 x的所有左子树显然都还是在x上不用管了 那么我们直接去合并x的右子树与y就可以了 反之亦然

边界就是x或y中只要有一个为零我们直接返回就好了


int merge(int x,int y)
{
	if(!x || !y) return x + y;
	if(pri[x] < pri[y])
	{
		ch[x][1] = merge(ch[x][1], y);
		pushup(x);
		return x;
	}
	else 
	{
		ch[y][0] = merge(x, ch[y][0]);
		pushup(y);
		return y;
	}
}


大家可能发现我上面的代码中有pushup这个东西 这是个啥?

下面给出pushup的code


void pushup(int x)
{
    siz[x] = 1 + siz[ch[x][1]] + siz[ch[x][0]];
}

这下明白了吧 也就是维护我们treap的大小(什么?不明白为什么要维护) 维护大小是为了便于去找排名 下面就要说

平衡树的操作里往往有查询序列里排名第k的数 具体如何实现?


int kth(int now,int k)
{
	while(1)
	{
		if(k <= siz[ch[now][0]])
		  now = ch[now][0];
		else if(k == siz[ch[now][0]] + 1) return now;
		else k -= siz[ch[now][0]] + 1, now = ch[now][1];
	}
}

非常好理解 我们先看看k是不是在左子树的范围 是的话就继续往下递归 否则的话如果它恰好为根我们皆大欢喜直接返回 不然的话我们减去左子树与根的大小

去右子树中找新的排名为k的数即可

由此我们基本实现了fhq 。。。

下面是洛谷P3369普通平衡树的AC code



#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <ctime> 


using namespace std;

const int N = 100010;

int val[N],pri[N];
int ch[N][3];
int siz[N],sz = 0;
int n;


inline int read()
{
	char ch = getchar();
	int f = 0, x = 0;
	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch - '0'), ch = getchar();
	if(f) x = -x;
	return x;
}

int new_node(int v)
{
    siz[++ sz] = 1;
    val[sz] = v;
    pri[sz] = rand();
    return sz;
}

void pushup(int x)
{
    siz[x] = 1 + siz[ch[x][1]] + siz[ch[x][0]];
}

void split(int now,int k,int &x,int &y)
{
	if(!now) x = y = 0;
    else
    {
    	if(val[now] <= k)
    	   x = now, split(ch[now][1], k, ch[now][1], y);
    	else
    	   y = now, split(ch[now][0], k, x, ch[now][0]);
    	pushup(now);
	}
}


int merge(int x,int y)
{
	if(!x || !y) return x + y;
	if(pri[x] < pri[y])
	{
		ch[x][1] = merge(ch[x][1], y);
		pushup(x);
		return x;
	}
	else 
	{
		ch[y][0] = merge(x, ch[y][0]);
		pushup(y);
		return y;
	}
}


int kth(int now,int k)
{
	while(1)
	{
		if(k <= siz[ch[now][0]])
		  now = ch[now][0];
		else if(k == siz[ch[now][0]] + 1) return now;
		else k -= siz[ch[now][0]] + 1, now = ch[now][1];
	}
}



int main()
{
	srand((unsigned) time(NULL));
	n = read();
	int root = 0, x, y, z;
	while(n --)
	{
		int op,a;
		op =read(), a = read();
		if(op == 1)
		{
			split(root, a, x, y);
			root = merge(merge(x, new_node(a)), y);
			
		}
		else if(op == 2)
		{
			split(root, a, x, z);
			split(x, a - 1, x, y);
		    y = merge(ch[y][1], ch[y][0]);
		    root = merge(merge(x, y), z);
		}
		else if(op == 3)
		{
			split(root, a - 1, x, y);
		    printf("%d\n", siz[x] + 1);
		    root = merge(x, y);
		}
		else if(op == 4)
	        printf("%d\n",val[kth(root, a)]);
	    else if(op == 5)
	    {
	    	split(root, a - 1,x, y);
	    	printf("%d\n",val[kth(x, siz[x])]);
	    	root = merge(x, y);
		}
		else 
		{
			split(root, a, x, y);
			printf("%d\n",val[kth(y, 1)]);
			root = merge(x, y);
		}
	}
	return 0;
}


然后就可以滚去做一点平衡树的题了吧 (菜鸡感叹)

未完待续。。。

posted @ 2021-11-13 09:26  Linyk  阅读(161)  评论(0编辑  收藏  举报