fhq treap(无旋treap) 学习笔记

首先最好要会写treap(也先了解一下笛卡尔树是什么。。。)
fhq treap和treap同样有一个随机分配的rnd值,用于平衡,但fhq treap不需要旋转操作来维持平衡,因为有两个神奇的操作merge和split

在两种操作之前,要明确的一点是fhq treap依靠rnd值来维护平衡,把每个点按照小根堆的方式放置,即根rnd小于子rnd,并且在任何时候都要保证有二叉搜索树的性质,即左子树key <= 根节点key <= 右子树key
其实我们可以把相等的点缩在一起看做根,这样就满足了左子树<根<右子树了,就算有一些和根相同的点跑到右子树上了,由于我们在各种操作的过程中是不断往右子树上跑的,所以这些点还是会考虑到


下面两个函数是fhq treap的核心,理解好了其他操作就十分简单
注意两个操作都要update(now)

Merge:

递归的过程,在执行之前需要保证x中所有点的key值(要存储的值,不是随机值)都小于y的(简称为x < y),这是二叉搜索树的性质,而如何保证这个性质先不用着急,在插入操作中,这个性质自然实现了。最后要返回当前根节点

int merge(int x, int y) { //将xy合并为一棵新树 返回当前树根
	if(!x || !y) return x + y; // 递归边界
	if(tr[x].rnd < tr[y].rnd) {
		tr[x].rs = merge(tr[x].rs, y);//把y接在x的右子树上
		update(x);
		return x;
	}
	tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子树上
	update(y);
	return y;
}

Split:

权值小于等于k的分到左树(x),大于的分到右树(y)。
注意参数中的x,y是“x树中等待和别的点相接的点,y树种等待和别的点相接的点”这个意思
等待这个词很微妙,下面通过一个具体的情形解释一下
首先x,y均为0,然后判断一下,确定目前x或y的树根
若目前now的值小于等于k,则now应该放在x树上,放在x树的哪里呢?就放在函数目前的参数上,即x“等待相接的位置”,并且x左子树均比k小,不需要改动。
然后现在整棵树变为两部分 now及其左子树,now的右子树,显然在上述情形下,下一次要划分的树就是now的右子树,并且按照二叉搜索树的性质应当把划分下来的,值仍然小于等于k的子树接在x的右子树上,所以此时now的右子树就是“x等待相接的位置”
另一种情况同理
并且不用担心这样分会错(比如在第一种情况中now的右节点还是原来的,但是在不断递归的过程中now的右节点要么被更新,要么由if(!now) x = y = 0;变为0)

void split(int now, int k, int &x, int &y) {
	if(!now) {
		x = y = 0;
		return; //一定要写return 不要调用到下面的代码,到这里为止了!
	} 
	if(tr[now].val <= k) 
		x = now, split(tr[now].rs, k, tr[now].rs, y);
	else 
		y = now, split(tr[now].ls, k, x, tr[now].ls);
	update(now);	//那些now = 0的点不能调用update,因为会使得本来siz为0的点变为1(看update代码,无论如何也会加1)
}

解决区间问题的时候要按size分
这时k为size 若now树的左子树有>=k个节点,那么就从其左子树中找k个
否则,从其右子树中找k-tree[lson].size-1个(左边贡献了足够多的点) 减去的1是根节点(有点像treap求第k大)

void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
	if(!now) x = y = 0;
	else {
		if(tr[now].lz) down(now);
		if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
			y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
		else 
			x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
		update(now);
	}
}

操作时注意实时维护root具体是哪个点

数组模拟开点

void new_node(int val) {
	tree[++tot].size = 1, tree[tot].val = val, tree[tot].rnd = rand();
	return tot;
}

插入

为了保证"x < y"这个性质,只好按val拆分,然后在合并x和点new的时候虽然x中有和new值相同的,但是问题不大,

split(root, val, x, y);
root = merge(merge(x, new_node(val)), y);

删除

注意合并的时候要反着拆分的顺序操作,仍然是为了保证"x < y"
下面的操作成功分出了一个c,而c的根的值一定为val,这是因为前面划分除出了一个a,a只有两种点,值为val的点和值<=val的点,而c又是从a中分出来的

split(root, val, a, b);
split(a, val-1, a, c);
c = merge(tree[c].lson, tree[c].rson);
root = merge(merge(a,c), b);//a,c最后拆,最先合

找值为val的rank

把整棵树以 val-1 split 分为x和y 然后答案就是x.size+1,当然前提是这个值存在

找rank为k的值

和treap一样

int kth(int now, int k) {
	while(1) {
		int ls = tr[now].ls, rs = tr[now].rs;
		if(k <= tr[ls].siz) {
			now = ls;
		} else {
			if(k == tr[ls].siz + 1) {
				return now;
			} else {
				k -= tr[ls].siz + 1;
				now = rs;
			}
		}
	}
}

找val的前驱

按val-1划分,左树里面找最大的,即在左树里面找rank为左树size的即可

找val的后继

按val划分,右树rank为1的就是

区间翻转

构造一棵treap,使得对treap中序遍历就能得到初始区间
其实应该模仿笛卡尔树,线性建树的,但是我不会,而且大多数题没卡到那个地步
一般就是那种,询问少,n很大,nlogn过不去,但是qlogn能过去的题,就得模仿笛卡尔树建树了,开个单调栈什么的,我目前还不会写。。。

int build(int l, int r) {
	if(l > r) return 0;
	int mid = l+r>>1;
	int pos = new_node(mid);
	tr[pos].ls = build(l, mid-1);
	tr[pos].rs = build(mid+1, r);
	update(pos);
	return pos;
}

划分两次,把树分出来一个l~r的,然后进行翻转操作,具体来说就是把一个点的左右儿子交换,对于翻转区间内每一个点都进行这么个操作,最后这个区间就被整体翻转了。这里有懒标记的思想,打上标记把树合并就行了

void reverse(int l, int r) {
	int a,b,c,d;
	split(root, r, a, b);
	split(a, l-1, c, d);
	tr[d].lz ^= 1;
	root = merge(merge(c, d), b);
}

输出:中序遍历
这里有懒标记的思想,所以输出时还要下传

void dfs(int now) {
	if(tr[now].lz) down(now);
	if(tr[now].ls) dfs(tr[now].ls);
	printf("%d ", tr[now].val);
	if(tr[now].rs) dfs(tr[now].rs);
}

有人说要设立两个哨兵节点处理边界。。。但是我没设立也过了,如果你知道为什么的话请在评论区指导一下我。。。


普通平衡树:

#include 
#include 
#include 
#include 
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 100000 + 10;
typedef long long ll;
int n,val,opt,a[MAXN],root,x,y,z,tot;

struct fhqtreap{
	int ls, rs, val, rnd, siz;
}tr[MAXN];

int newnode(int x) {
	tr[++tot].rnd = rand();
	tr[tot].val = x;
	tr[tot].siz = 1;
	return tot;
}

void update(int now) {
	tr[now].siz = tr[tr[now].ls].siz + tr[tr[now].rs].siz + 1;
}

int merge(int x, int y) {
	if(!x || !y) return x + y;
	if(tr[x].rnd < tr[y].rnd) {
		tr[x].rs = merge(tr[x].rs, y);
		update(x);
		return x;
	}
	tr[y].ls = merge(x, tr[y].ls);
	update(y);
	return y;
}

void split(int now, int k, int &x, int &y) {
	if(!now) {
		x = y = 0;
		return; 
	} 
	if(tr[now].val <= k) 
		x = now, split(tr[now].rs, k, tr[now].rs, y);
	else 
		y = now, split(tr[now].ls, k, x, tr[now].ls);
	update(now);	
}

int kth(int now, int k) {
	while(1) {
		int ls = tr[now].ls, rs = tr[now].rs;
		if(k <= tr[ls].siz) {
			now = ls;
		} else {
			if(k == tr[ls].siz + 1) {
				return now;
			} else {
				k -= tr[ls].siz + 1;
				now = rs;
			}
		}
	}
}

int main() {
	scanf("%d", &n);
	while(n--) {
        scanf("%d%d", &opt, &val);
        if(opt == 1) {
        	
        	split(root, val, x, y);
        	root = merge(merge(x, newnode(val)), y);
        	
        } else if(opt == 2) {
        	
        	split(root, val, x, y); 
        	split(x, val-1, x, z);
        	z = merge(tr[z].ls, tr[z].rs);
        	root = merge(merge(x, z), y);
        	
        } else if(opt == 3) {
        	
        	split(root, val, x, y);
        	split(x, val-1, x, z);
        	printf("%d\n", tr[x].siz+1);
        	merge(merge(x, z), y);
        	
        } else if(opt == 4) {
        	
        	printf("%d\n", tr[kth(root, val)].val);
        	
        } else if(opt == 5) {
        	
        	split(root, val-1, x, y);
        	printf("%d\n", tr[kth(x, tr[x].siz)].val);
        	root = merge(x, y);
        	
        } else {
        	
        	split(root, val, x, y);
        	printf("%d\n", tr[kth(y, 1)].val);
        	root = merge(x, y);
        	
        }
    }
	return 0;
}

文艺平衡树:

#include 
#include 
#include 
#include 
using namespace std;

const int MAXN = 100000 + 10;

struct trnode{
    int val,lz,rnd,ls,rs,siz;
}tr[MAXN];

int n,m,tot,root;

void update(int now) {
    tr[now].siz = tr[tr[now].ls].siz + tr[tr[now].rs].siz + 1;
}

void down(int now) {
    tr[now].lz ^= 1;
    swap(tr[now].ls, tr[now].rs);
    tr[tr[now].ls].lz ^= 1;
    tr[tr[now].rs].lz ^= 1;
}

int new_node(int val) {
    tr[++tot].val = val;
    tr[tot].siz = 1;
    tr[tot].rnd = rand();
    tr[tot].lz = 0; //虽然原来就是0,但是仍然建议写上,当有多次询问时,我们就可以不用memset,而是直接把tot置为0,但是这样就要初始化lz
    return tot;
}

int build(int l, int r) {
    if(l > r) return 0;
    int mid = l+r>>1;
    int pos = new_node(mid);
    tr[pos].ls = build(l, mid-1);
    tr[pos].rs = build(mid+1, r);
    update(pos);
    return pos;
}

int merge(int x, int y) { //将xy合并
    if(!x || !y) return x + y; // 递归边界
    if(tr[x].lz) down(x);//下推之前 要先检验是否有,因为用的是异或清理标记,不能随便下推了
    if(tr[y].lz) down(y);
    if(tr[x].rnd < tr[y].rnd) { 
        tr[x].rs = merge(tr[x].rs, y);//把y接在x的右子树上
        update(x);
        return x;
    } else {
        tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子树上
        update(y);
        return y;
    }
}

void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
    if(!now) x = y = 0;
    else {
        if(tr[now].lz) down(now);
        if(k <= tr[tr[now].ls].siz) //画画图理解一下,k在now的左子树里就执行下面的语句
            y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
        else 
            x = now, split(tr[now].rs, k-tr[tr[now].ls].siz-1, tr[now].rs, y);
        update(now);
    }

}

void rever(int l, int r) {
    int a,b,c,d;
    split(root, r, a, b);
    split(a, l-1, c, d);
    tr[d].lz ^= 1;
    root = merge(merge(c, d), b);
}

void dfs(int now) {
    if(tr[now].lz) down(now);
    if(tr[now].ls) dfs(tr[now].ls);
    printf("%d ", tr[now].val);
    if(tr[now].rs) dfs(tr[now].rs);
}

int main() {
    scanf("%d %d", &n, &m);
    root = build(1, n);
    for(int i=1; i<=m; i++) { 
        int l,r;
        scanf("%d %d", &l, &r);
        rever(l, r);
    }
    dfs(root);
    return 0;
}

你可能感兴趣的:(NOIP,数据结构)