非旋TREAP

发布时间 2023-06-13 22:56:43作者: 铃狐sama

非旋TREAP

一、TREAP—新的树形结构

1.treap=tree+heap

首先可以知道的是,他作为一个树形结构,很多操作都可以达到logn的强度,这是tree的来源

其次,treap实际上(个人觉得)很像一个二叉搜索树,换句话说,我们建立这颗数时要有一个值,(假设这个值叫做key吧),那么要满足一个点左子树内的点的key都小于它,右子树内的点的key都大于它。

最后,我们还可以人为安排一个值(这里称为rank),在建立树的时候可以去满足任何一棵子树必须是rank值最小的点作为根。

2.更优秀的时间复杂度

我们知道搜索树的时间复杂度和这个树的高度(n)成正比,为了尽力压缩这个高度,我们可以给rand值赋值为随机值

注意:这样做的话,treap会失去一个安排最小值的功能,交换了时间复杂度的更优秀

这是什意思呢,看这道题,:[P3165 CQOI2014]排序机械臂 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

由于我要寻找这个无序序列中的最小值,我的rand不能随机而是要赋值为输入的val,这样可以保证最小的val 在根节点处,但也因此时间复杂度会大大增加。

不理解?没事,先往下看,慢慢来。

3.treap的基础打法

为了更好地记忆,我认为最基础的应该分为一下几个函数来记忆

1.基本框架-开数组

struct node{
	int val;//值
	int sz;//大小
	int ls;//左儿子
	int rs;//右儿子
	int rk;//保证根节点的值,上面讲到过
}tree[200005];

记住了,五个基本的量(sz的应用很多,所以尽量要打上)

还有,一定要清楚地辨析key和val ,这两个是不一定等的(但是一般key就是val,按val 大小建树)

2.动态开点

int newnode(int v){
    tree[++st].sz=1;
    tree[st].val=v;
    tree[st].rk=rand();
    return st;
}

代码的意思是,建立一个代号为st,st的val 为v的点

为了节约时间复杂度,利用随机化函数rand()来给rk;

注意:rand不能作为数组的名字,会G掉,但DEV c++查不出来

注意:后面会讲到有关pushdown的东西,一定要注意初始化,总之就是除了rs,ls,其他的都尽量在这里给初始值,不然都不知道错在哪里

3.sz的维护

void pushup(int root){
	int l=tree[root].ls;
	int r=tree[root].rs;
	tree[root].sz=tree[r].sz+tree[l].sz+1;
	return ;
}

很容易,类比线段树pushup

4.分离和统一

死记硬背就好,顺序不能写错。

split用于拆分一颗树,返回拆成两个数的根(&x,&y)

merge则是合并两个树,返回合成的新根

当然了,这里的split是按sz的大小来分的(分裂为刚好siz=k的子树和剩下的子树),还可以按照val的大小来分

注意:如果要按照val分的话,我树的key必须就是val,否则的话val是无序的不能分

注意,按照val分裂是小于等于,按照sz分裂的话就是小于,但merge都一样

注意:使用merge时都要root=merge(r1,r2),千万别漏了root=这个东西

!!!注意!!!,我merge(a,b)时,a的key必须小于b的key

按照sz来分裂

int merge(int x,int y){
	if(x==0){
		return y;
	}
	if(y==0){
		return x;
	}
	if(tree[x].rk<tree[y].rk){
		pushdown(x);
		tree[x].rs=merge(tree[x].rs,y);
		pushup(x);
		return x;
	}
	else{
		pushdown(y);
		tree[y].ls=merge(x,tree[y].ls);
		pushup(y);
		return y;
	}
}
void split(int rt,int k,int &x,int &y)
{
    if(rt==0){
        x=y=0;
        return;
    }
    pushdown(rt);
    if(tree[tree[rt].ls].sz<k){
    	x=rt;
		split(tree[rt].rs,k-tree[tree[rt].ls].sz-1,tree[rt].rs,y);
	}
    else{
        y=rt;
		split(tree[rt].ls,k,x,tree[rt].ls);	
	}    
    pushup(rt);
}

按照val来分裂的:

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

4.treap的进阶使用

1.treap相比于线段树的好处

为什么要用treap呢,这也关系到进阶使用的一些理解问题

1.好理解,好拆分,好合并,随意加点

2.相比线段树而言,它支持区间翻转,而线段树就很难受(12345翻转2-3得14325),下一个(2)中我会说明

2.在treap上pushup和pushdown

1.总体规则

pushdown和pushup有一个总体规则:

1.在自己做了标记的事后下传标记,但儿子不做。

什么意思?例如我当前节点为x,它的儿子设为ls,rs.如果我现在x的翻转标记为1(要翻转),那么我就swap(ls,rs),然后下传lz至ls和rs,但不继续往下做了

2.位置在断/合并前后

什么意思,我的pushup在split中可以看到还没开始断开就down了

if(rt==0){
        x=y=0;
        return;
    }
    pushdown(rt);
......

在merge中,也是在没有merge前down了

int merge(int x,int y){
	if(x==0){
		return y;
	}
	if(y==0){
		return x;
	}
	if(tree[x].rk<tree[y].rk){
		pushdown(x);
		tree[x].rs=merge(tree[x].rs,y);
		pushup(x);
		return x;
	}
	else{
		pushdown(y);
		tree[y].ls=merge(x,tree[y].ls);
		pushup(y);
		return y;
	}
}

至于pushup,位置不用修改,直接在维护sz的pushup中维护就行了

注意:个人觉得,为了思路清晰,最好多写几个函数

代码(pushdown)
void pushdown(int rt){
	if(tree[rt].lz==1){
		swap(tree[rt].ls,tree[rt].rs);
		if(tree[rt].ls){
			change(tree[rt].ls);
		}
		if(tree[rt].rs){
			change(tree[rt].rs);
		}
		tree[rt].lz=0;
	}
}

个人觉得,后面很多题都有很多的lz,很多种操作,因此,可以吧swap那里单独拿出来写个函数

比如,我要是又有个某个区间整体赋为某个值得lzval,我就多写个if(lzval!=-1)cover(ls),cover(rs);思路清晰一些

二、课后习题以及一些思路

P3369 【模板】普通平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P2596 ZJOI2006]书架 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P1110 ZJOI2007] 报表统计 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P2234 HNOI2002]营业额统计 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P2584 ZJOI2006]GameZ游戏排名系统 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P1486 NOI2004] 郁闷的出纳员 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

以上是没有pushdown就能完成的

以下是综合应用(大多数要up和down)

P3391 【模板】文艺平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

(难到爆,打对了你就赢了)[P2042 NOI2005] 维护数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

(小难,但还好)[P2572 SCOI2010] 序列操作 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P3165 CQOI2014]排序机械臂 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P2073 送花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

(这个是难上加了一点点难度)P2710 数列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)