平衡树学习笔记

平衡树学习笔记

一,二叉查找树(BST)

首先,二叉查找树是一颗二叉树,每个节点有一个关键码,它满足如下性质

  1. 一个节点的关键码不小于它的左子树的关键码。
  2. 一个节点的关键码不大于他的右子树的关键码。

因此,一个二叉查找树的中序遍历就是一个单调非递增的序列。

在一个二叉查找树中,一个关键码\(x\)的前驱/后继分别表示在这个BST中,最大的小于\(x\)的关键码和最小的大于\(x\)的关键码。

(1)建立:

我们定义一个结构体,其中\(l,r\)表示其左右儿子的下标(没有的话为0),\(val\)表示它的关键码。

为了方便起见,我们在一开始在BST中构建两个点,分别为\(-INF\)\(INF\),由这两个点构成一个初始的BST

const int N=1e5+5; 
const int INF=0x3f3f3f3f3f3f3f3f;
struct BST{
    int l,r,val;
}a[N];
int tot,root;
int New(int val){
    a[++tot].val=val;
    return tot;
}
void build(){
    root=New(-INF);
    a[root].r=New(INF);
}

(2)检索:

用于查找BST中是否有关键码为\(x\)的点。

bool get(int k,int x){
    if(!k) return 0; 
    if(a[k].val==x) return 1;
    return a[k].val<x?get(a[k].r,x):get(a[k].l,x);
}

(3)插入:

用于在BST中插入一个关键码为\(x\)的点。(此前没有一个关键码为\(x\)的)

void insert(int &k,int x){
    if(!k){
        k=New(x);
        return;
    }
    if(x<a[k].val) insert(a[k].l,x);
    else insert(a[k].r,x);
}

(4)删除:

用于在BST中删除一个关键码为\(x\)的点。(此前有一个关键码为\(x\)的)

首先,我们找到关键码为\(x\)的点,设其下标为\(k\)

若那个点子树数量小于2,删除点\(k\),用它的儿子代替它。

若那个点子树数量为2,我们可以找到它的后继节点,设其为\(nxt\)

由于\(nxt\)不可能有左子树,我们直接删除\(nxt\),并且让\(nxt\)代替\(k\)

void remove(int &k,int x){
    if(!k) return;
    if(a[k].val==x){
        if(!a[k].l) k=a[k].r;
        else if(!a[k].r) k=a[k].l;
        else{
            int nxt=a[k].r;
            while(a[nxt].l) nxt=a[nxt].l;  
            remove(a[k].r,a[nxt].val);
            a[nxt].r=a[k].r,a[nxt].l=a[k].l;
            k=nxt;
        }
    }
    else if(a[k].val>x) remove(a[k].l,x);
    else remove(a[k].r,x);
}

(5)例题(二叉查找树-FZUOJ):

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std; 

const int N=1e5+5; 
const int INF=0x3f3f3f3f3f3f3f3f;
struct BST{
    int l,r,val;
}a[N];
int tot,root;
int New(int val){
    a[++tot].val=val;
    return tot;
}
void build(){
    root=New(-INF);
    a[root].r=New(INF);
}
bool get(int k,int x){
    if(!k) return 0; 
    if(a[k].val==x) return 1;
    return a[k].val<x?get(a[k].r,x):get(a[k].l,x);
}
void insert(int &k,int x){
    if(!k){
        k=New(x);
        return;
    }
    if(x<a[k].val) insert(a[k].l,x);
    else insert(a[k].r,x);
}
void remove(int &k,int x){
    if(!k) return;
    if(a[k].val==x){
        if(!a[k].l) k=a[k].r;
        else if(!a[k].r) k=a[k].l;
        else{
            int nxt=a[k].r;
            while(a[nxt].l) nxt=a[nxt].l;  
            remove(a[k].r,a[nxt].val);
            a[nxt].r=a[k].r,a[nxt].l=a[k].l;
            k=nxt;
        }
    }
    else if(a[k].val>x) remove(a[k].l,x);
    else remove(a[k].r,x);
}
int n;
signed main(){
	cin>>n;
	build();
	char opt;int x;
	while(n--){
		cin>>opt>>x;
		if(opt=='D'){//删除 
			if(!get(root,x))puts("not exist");
			else{
				puts("delete success");
				remove(root,x);
			}
		}
		else{//插入 
			if(get(root,x))puts("has been");
			else{
				puts("insert success");
				insert(root,x);
			}
		}
	}
	return 0;
}

二,FHQ-Treap(无旋Treap)

参考文章:FHQ-Treap学习笔记 - 洛谷专栏

首先,Treap是同时满足二叉查找树和堆(以下默认为小根堆)的性质的,所以Treap的结构是唯一的,并且对于一棵Treap中的所有点及其子树都满足上述性质,那么Treap的子树仍然是Treap,一棵Treap中的某一个点及其子树组成的树仍是Treap。

一个数的排名表示在Treap按照中序遍历变为的单调非递增序列中,这个数是第几个。(相当于若中序遍历的话,它是第几个遍历到的)。

(1)建立:

我们定义一个结构体,其中\(l,r\)表示其左右儿子的下标(没有的话为0),\(val\)表示它的要满足二叉查找树性质的值,\(rnd\)表示它的要满足堆性质的值,\(siz\)表示以它为根的子树大小。

若是按照排名分裂,还需要一个\(fa\)记录他的父节点。注意,分裂后不会再次合并,这就需要清空分裂后两个\(root\)\(fa\)

const int N=1e5+5; 
const int INF=0x3f3f3f3f3f3f3f3f;
struct FHQ_Treap{
    int l,r,val,rnd,siz;
    //int fa;
}a[N];
int tot,root;
int New(int val){
    a[++tot].val=val;
    a[tot].siz=1;
    a[tot].rnd=rand()*rand();
    return tot;
}

(2)对Treap进行分裂、合并:

通过对treap进行分裂、合并操作,我们可以进行插入,求前驱,求后继...操作。

分裂:

我们可以把一颗Treap按照\(val\)分裂为\(\le val\)的部分和\(>val\)的部分。

void split(int val,int k,int &x,int &y){//按照val来分,原来的treap为k,分裂后的分别为x,y
    if(!k){
        x=y=0;
        return;//到了空节点
    }
    if(a[k].val<=val){//k及其左儿子都应该被分裂入x。
        x=k;
        split(val,a[k].r,a[k].r,y);//由于k的右儿子的val一定不小于a[k].val,所以x直接开始更新右儿子。
        push_up(x);
    }
    else{//与上面同理
        y=k;
        split(val,a[k].l,x,a[k].l);
		push_up(y);
    }
}
//主函数内:
int k,x,y,root;
//do something ...
split(k,root,x,y);

若我们想将区间\([l,r]\)中分裂出来一个在\([ql,qr]\)中Treap的,则这样搞:

//主函数内:
int k,x,y,root,ql,qr;
//do something ...
split(qr,root,x,y);//将[l,r]分为[l,qr],[qr,r];
split(ql-1,x,x,y);//将[l,qr]分为[l,ql-1],[ql,qr]

除此之外,我们还可以按照排名进行分裂,道理同上,代码如下:

void split(int now,int k,int &x,int &y){//将now按照排名k分为x,y两颗
    if(!now){
        x=y=0;
        return;
    }
    if(a[a[now].l].siz>=k){//排名为k的在左子树,则x不变,y继承now及其右子树
        y=now;
        split(a[now].l,k,x,a[y].l);
    }
    else{//排名为k的在右子树,x继承now以及左子树,y不变
        x=now;
        split(a[now].r,k-a[a[k].l].siz-1,a[x].r,y);
    }
    push_up(now);
}

合并:

int merge(int x,int y){//将x,y两棵合并为一颗,返回下标(满足x的val的最大值<=y的val的最小值)
    if(!x||!y) return x|y;
    if(a[x].rnd<a[y].rnd){//满足其堆的性质,此时x为根
        a[x].r=merge(a[x].r,y);//由于那个条件,y只可能在x的右子树。
        push_up(x);//记得上传
        return x;
    }
    else{//此时y为根
        a[y].l=merge(x,a[y].l);//同理
        push_up(y);
        return y;
    }
}

//主函数内:
int x,y,root;
//do something ...
root=merge(x,y);

(3)通过分裂与合并实现某些操作:

插入

插入一个\(val\)\(val\)的节点。

//主函数内:
split(val,root,x,y);
root=merge(merge(x,New(val)),y);

删除

删除一个\(val\)\(val\)的节点:

//主函数内:
split(val,root,x,y);
split(val-1,x,x,z);
//z所在的子树内的val都为val
z=merge(a[z].l,a[z].r);//删除了这个根节点
root=merge(merge(x,z),y);

删除所有\(val\)\(val\)的节点:

//主函数内:
split(val,root,x,y);
split(val-1,x,x,z);
root=merge(x,y);

查询排名:

若是按照权值分裂的,查找权值为\(val\)的节点的排名:

我们直接将treap分裂为两颗,分别代表\([l,val-1][val,r]\),然后左树的大小+1即可。

int find(int val){
    split(val-1,root,x,y);
    int ans=a[x].siz+1;
    root=merge(x,y);
    return ans;
}

若是按照排名分裂的,查找下标为\(now\)的节点的排名:

我们从节点\(now\)开始往\(root\)跳。

\(now\)为左儿子,先于\(a[now].fa\),答案不变。

否则,在他前面有左儿子和\(fa\),更新答案即可。

int find(int now){
    int ans=a[a[now].l].siz+1;//注意+1
    while(now!=root){
        if(now==a[a[now].fa].r) ans+=a[a[now].fa]].siz-a[now].siz;
        now=a[now].fa;
    }
    return ans;
}

查找排名为\(k\)的数:(保证存在)

int kth(int x,int k){//返回下标
    while(1){
        if(a[a[x].l].siz>=k) x=a[x].l;//左子树内
        else if(a[a[x].l].siz+1==k) return x;//找到了
        else k-=a[a[x].l].size+1,x=a[x].r;//右子树内,注意:先减后走
    }
}

查找前驱:

int find_pre(int val){
    split(val-1,root,x,y);
    int ans=a[kth(x,a[x].siz)].val;
    root=merge(x,y);
    return ans;
}

查询后继:

int find_nxt(int val){
    split(val,root,x,y);
    int ans=a[kth(y,1)].val;
    root=merge(x,y);
    return ans;
}

(4)下传与上传:

类似于线段树,我们的FHQ-Treap可以对标记或维护的信息进行上传、下传的操作。

上传:

对每个节点的左右儿子维护的信息(如大小)进行上传,同时也可下传父节点这样的信息:

void push_up(int k){
	a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1;
	a[k].sum=a[k].val+a[a[k].l].sum+a[a[k].r].sum; 
	if(a[k].l) a[a[k].l].fa=k;
	if(a[k].r) a[a[k].r].fa=k;
}

下传:

如同线段树一样,对于区间操作,是不能一个个进行修改操作的,我们需要懒标记来记录一个区间操作的暂存信息。

每次的询问的操作若对 FHQ-Treap 的结构(维护翻转区间时),维护的信息(区间修改)有要求时,都要先下传。

需要注意的是,同线段树不同的是,FHQ-Treap的区间翻转的懒惰标记不仅代表其子节点是否需要翻转,也代表他自己是否需要。

(也就是说在打区间翻转的懒惰标记后不需要给这个节点的左右儿子翻转)。

类似于线段树,我们在向下递归前把会在这一层发生变化的节点的懒惰标记下传。

void push_down(int k){
	if(!a[k].tag) return;
	if(a[k].l) a[a[k].l].tag^=1;
	if(a[k].r) a[a[k].r].tag^=1;
	swap(a[k].l,a[k].r);
	a[k].tag=0; 
} 

特别的,若我们是排名分裂,在进行查找给定点的排名操作时,若下放会改变子树大小或左右儿子位置(如区间翻转),需要先从给定点回溯到根,再从根逐层下传标记

void down(int k){//依次下放 
	if(a[k].fa) down(a[k].fa);
	push_down(k);
}

(5)可持久化:

FHQ-Treap的可持久化类似于线段树的,在合并和分裂的时候,我们在向下递归前,对在这一层信息改变的点建立新节点,没有改变的点维持原状,将新点连向不需要更改的点即可,具体代码如下:

int New(int k){
	a[++tot]=a[k];
	return tot;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}	
	if(a[now].val<=k){ 
		x=New(now);
		split(a[now].r,k,a[x].r,y);
		push_up(x);
	}
	else{
		y=New(now);
		split(a[now].l,k,x,a[y].l);
		push_up(y);
	}
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(a[x].rnd<a[y].rnd){
		x=New(x);
		a[x].r=merge(a[x].r,y);
		push_up(x);
		return x;
	}
	else{
		y=New(y);
		a[y].l=merge(x,a[y].l);
		push_up(y);
		return y;
	}
}

不过,若有标记下传,我们在下传前一定要对左右儿子新建一个节点,不然有可能会影响到以前的版本。(若当前的儿子和以前的共用,而这个标记是在当前版本打上的)。

注意不要对0号节点进行新建, 否则可能导致其他操作一直递归。

具体代码如下:

void push_down(int k){
    if(!a[k].tag) return;
    a[k].tag=0;
    if(a[k].l){
        a[k].l=New(a[k].l);a[a[k].l].tag^=1;
    }
    if(a[k].r){
        a[k].r=New(a[k].r);a[a[k].r].tag^=1;
    }
    swap(a[k].l,a[k].r);
}

空间复杂度:

我们每次调用\(merge\)或者\(split\)操作的时候,会新建树高个节点,树高期望是\(\log n\)的,不过有些时候可能略大。

另外注意,每次调用\(push\_down\)函数中也会新建最多两个节点,以及进行一次操作会调用多次\(merge\)\(split\)函数。

所以,我们的空间一定要开大一点,不要抵着开!!

(6)例题:

1,【模板】普通平衡树 - 洛谷(权值分裂模板):

直接FHQ-Treap进行一系列操作即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
struct FHQ_Treap{
    int l,r,val,rnd,siz;
}a[N];
int tot,n;
int New(int val){
    a[++tot].val=val;
    a[tot].siz=1;
    a[tot].rnd=rand()*rand();
    return tot;
}
void push_up(int k){
    a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz;
}
void split(int val,int k,int &x,int &y){//按照val来分,原来的treap为k,分裂后的分别为x,y
    if(!k){
        x=y=0;
        return;//到了空节点
    }
    if(a[k].val<=val){//k及其左儿子都应该被分裂入x。
        x=k;
        split(val,a[k].r,a[k].r,y);//由于k的右儿子的val一定不小于a[k].val,所以x直接开始更新右儿子。
        push_up(x);
    }
    else{//与上面同理
        y=k;
        split(val,a[k].l,x,a[k].l);
		push_up(y);
    }
}
int merge(int x,int y){//将x,y两棵合并为一颗,返回下标(满足x的val的最大值<=y的val的最小值)
    if(!x||!y) return x|y;
    if(a[x].rnd<a[y].rnd){//满足其堆的性质,此时x为根
        a[x].r=merge(a[x].r,y);//由于那个条件,y只可能在x的右子树。
        push_up(x);//记得上传
        return x;
    }
    else{//此时y为根
        a[y].l=merge(x,a[y].l);//同理
        push_up(y);
        return y;
    }
}
int kth(int x,int k){//返回下标
    while(1){
        if(a[a[x].l].siz>=k) x=a[x].l;//左子树内
        else if(a[a[x].l].siz+1==k) return x;//找到了
        else k-=a[a[x].l].siz+1,x=a[x].r;//右子树内
    }
}

int x,y,z,k,root;
int find_pre(int val){
    split(val-1,root,x,y);
    int ans=a[kth(x,a[x].siz)].val;
    root=merge(x,y);
    return ans;
}
int find_nxt(int val){
    split(val,root,x,y);
    int ans=a[kth(y,1)].val;
    root=merge(x,y);
    return ans;
}
int find(int val){
    split(val-1,root,x,y);
    int ans=a[x].siz+1;
    root=merge(x,y);
    return ans;
}

signed main(){
	scanf("%lld",&n);
	int opt,x,val;
	while(n--){
		scanf("%lld%lld",&opt,&val);
		if(opt==1){
			split(val,root,x,y);
			root=merge(merge(x,New(val)),y);
		}
		if(opt==2){
			split(val,root,x,y);
			split(val-1,x,x,z);
			//z所在的子树内的val都为val
			z=merge(a[z].l,a[z].r);//删除了这个根节点
			root=merge(merge(x,z),y);
		}
		if(opt==3){
			printf("%lld\n",find(val));
		}
		if(opt==4){
			printf("%lld\n",a[kth(root,val)].val);
		}
		if(opt==5){
			printf("%lld\n",find_pre(val));
		}
		if(opt==6){
			printf("%lld\n",find_nxt(val));
		}
	}
	return 0;
}
2, [ZJOI2006] 书架 - 洛谷(排名分裂模板):

需要按照排名进行操作。

操作一:

设节点的排名为\(k\)(相当于书架上从上到下的第几个)。

我们可以将Treap分为排名在\([1,k-1],[k,k],[k+1,n]\)的三个Treap。

合并时再按照\([k,k][1,k-1][k+1,n]\)合并

操作二:

同操作一,合并时为\([1,k-1][k+1,n][k,k]\)

操作三:

先询问权值为\(s\)的节点的排名,设其为\(k\)

\(t=0\),不变。

\(t=1\),则先分割为\([1,k-1][k,k][k+1,k+1][k+2,n]\),然后合并为\([1,k-1][k+1,k+1][k,k][k+2,n]\)

\(t=-1\),则先分割为\([1,k-2][k-1,k-1][k,k][k+1,n]\),然后合并为\([1,k-2][k,k][k-1,k-1][k+1,n]\)

操作四:

询问权值为\(s\)的节点的排名-1。

操作五:

询问排名为\(s\)的节点。

代码小细节:我们可以用一个\(id[x]\)表示权值为\(x\)的点在\(treap\)中的下标。

代码:

#include<bits/stdc++.h>
using namespace std;
inline int rd(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int N=8e4+5;
int n,m;
int id[N];
int root,rt1,rt2,rt3,rt4,k;
struct FHQ_Treap{
	int val,rnd,l,r,siz,fa;
}a[N];
int tot; 
int New(int val){
	a[++tot]=(FHQ_Treap){val,rand()*rand(),0,0,1,0};
	id[val]=tot;
	return tot;
}
void push_up(int k){
    if(a[k].l) a[a[k].l].fa=k;
    if(a[k].r) a[a[k].r].fa=k;
    a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}
	if(a[a[now].l].siz>=k){
		y=now;
		split(a[now].l,k,x,a[y].l);
	}
	else{
		x=now;
		split(a[now].r,k-a[a[now].l].siz-1,a[x].r,y);
	}
	push_up(now);
}
int merge(int x,int y){
	if(!x||!y) return x|y;
	if(a[x].rnd<a[y].rnd){
		a[x].r=merge(a[x].r,y);
		push_up(x);
		return x;
	}
	else{
		a[y].l=merge(x,a[y].l);
		push_up(y);
		return y;
	}
}
int find(int now){//查找节点now的排名 
    int ans=a[a[now].l].siz+1;
    while(now!=root){
        if(now==a[a[now].fa].r) ans+=a[a[now].fa].siz-a[now].siz;
        now=a[now].fa;
    }
    return ans;
}
int kth(int x,int k){//查找排名为k的节点 
	while(1){
		if(a[a[x].l].siz>=k) x=a[x].l;
		else if(a[a[x].l].siz+1==k) return x;
		else k-=a[a[x].l].siz+1,x=a[x].r;
	}
}

int main(){
	srand(time(0));
	n=rd(),m=rd();
	string ch;int s,t;
	for(int i=1;i<=n;i++){
		s=rd();
		root=merge(root,New(s));
	}
	while(m--){
		cin>>ch;s=rd();
		if(ch[0]=='T'){
			k=find(id[s]);
			split(root,k,rt1,rt2);
			split(rt1,k-1,rt1,rt3);
			root=merge(merge(rt3,rt1),rt2);
		}
		if(ch[0]=='B'){
			k=find(id[s]);
			split(root,k,rt1,rt2);
			split(rt1,k-1,rt1,rt3);
			root=merge(rt1,merge(rt2,rt3));
		}
		if(ch[0]=='I'){
			t=rd();
			if(t==0) continue;
			k=find(id[s]);
			if(t==1){
				split(root,k,rt1,rt2);
				split(rt1,k-1,rt1,rt3);
				split(rt2,1,rt2,rt4);
				root=merge(merge(rt1,rt2),merge(rt3,rt4));
			}
			else{
				split(root,k-1,rt1,rt2);
				split(rt1,k-2,rt1,rt3);
				split(rt2,1,rt2,rt4);
				root=merge(merge(rt1,rt2),merge(rt3,rt4));
			}
		}
		if(ch[0]=='A'){
			k=find(id[s]);
			printf("%d\n",k-1);
		} 
		if(ch[0]=='Q'){
			printf("%d\n",a[kth(root,s)].val);
		}
	}
	return 0;
}
3,银河英雄传说V2 - 洛谷(排名分裂,查询区间和)

操作一:合并\(x,y\)所在的Treap

操作二:查询\(x\)的排名(设其为\(k\)),将\(x\)所在的Treap分裂为\([1,k-1][k,n]\)

操作三:先分裂,然后求Treap的和,然后再合并。

需要注意的是,我们在进行了操作二时,分裂后不会再次合并,这就需要清空分裂后两个\(root\)\(fa\)

我们可以将\(x\)就存在下标为\(x\)的地方,方便访问。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int rd(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=2e5+5;
int n,m;
struct FHQ_Treap{
	int l,r,fa,siz,rnd;
	ll sum,val;
}a[N];
int tot;
int New(ll val){
	a[++tot]={0,0,0,1,rand()*rand(),val,val};
	return tot;
} 
int get_rt(int x){
	while(a[x].fa) x=a[x].fa;
	return x;
}
int find(int x){//查询下标为x的节点的排名 
	int ans=a[a[x].l].siz+1;
	while(a[x].fa){
		if(a[a[x].fa].r==x) ans+=a[a[x].fa].siz-a[x].siz;
		x=a[x].fa; 
	}
	return ans;
}
void push_up(int k){
	a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1;
	a[k].sum=a[k].val+a[a[k].l].sum+a[a[k].r].sum; 
	if(a[k].l) a[a[k].l].fa=k;
	if(a[k].r) a[a[k].r].fa=k;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}
	if(a[a[now].l].siz>=k){
		y=now;split(a[now].l,k,x,a[y].l);
	}
	else{
		x=now;split(a[now].r,k-1-a[a[now].l].siz,a[x].r,y);
	}
	push_up(now);
} 
int merge(int x,int y){
	if(!x||!y) return x|y;
	if(a[x].rnd<a[y].rnd){//x为根 
		a[x].r=merge(a[x].r,y);
		push_up(x);
		return x;
	}
	else{//y为根 
		a[y].l=merge(x,a[y].l);
		push_up(y);
		return y;
	} 
}
int main(){
	int x,y,k,kk,rt,rt1,rt2,rt3;ll val;
	srand(time(0));
	n=rd(),m=rd();
	for(int i=1;i<=n;i++){
		val=rd();
		New(val); 
	}
	char opt;
	while(m--){
		cin>>opt;
		if(opt=='M'){
			x=rd(),y=rd();
			x=get_rt(x),y=get_rt(y);
			if(x==y) continue;
			merge(y,x);
		}
		else if(opt=='D'){
			x=rd();
			k=find(x);rt=get_rt(x);
			split(rt,k-1,rt,x);
			a[rt].fa=a[x].fa=0;
		}
		else{
			x=rd(),y=rd();
			if(get_rt(x)!=get_rt(y))puts("-1");
			else{
				rt=get_rt(x);
				k=find(x);kk=find(y);
				if(k>kk) swap(k,kk);
				split(rt,kk,rt1,rt3);
				split(rt1,k-1,rt1,rt2);
				printf("%lld\n",a[rt2].sum);
				merge(rt1,merge(rt2,rt3));
			}
		}
	}
	return 0;
}
4,【模板】文艺平衡树 - 洛谷

我们维护一个懒惰标记, 表示这个子树需要被反转。

然后在\(push\_down\)函数中,若这个节点的\(tag\)为1,则交换他的左右儿子,并且标记下传。

注意,\(split\)中,应该先下传原树的标记,再搞其他的。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int rd(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int N=1e5+5;
struct FHQ_Treap{
	int l,r,val,rnd,tag,siz;
}a[N];
int tot;
int New(int val){
	a[++tot].val=val;a[tot].rnd=rand();a[tot].siz=1;
	return tot;
} 
void push_down(int k){
	if(!a[k].tag) return;
	if(a[k].l) a[a[k].l].tag^=1;
	if(a[k].r) a[a[k].r].tag^=1;
	swap(a[k].l,a[k].r);
	a[k].tag=0; 
} 
void push_up(int k){
	a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}
	push_down(now); //在这里下传,
	if(a[a[now].l].siz>=k){
		y=now;
		split(a[now].l,k,x,a[y].l);
	}
	else{
		x=now;
		split(a[now].r,k-a[a[now].l].siz-1,a[x].r,y);
	}
	push_up(now);
}
int merge(int x,int y){
	if(!x||!y) return x|y;
	if(a[x].rnd<a[y].rnd){
		push_down(x);
		a[x].r=merge(a[x].r,y);
		push_up(x); 
		return x;
	}
	else{
		push_down(y);
		a[y].l=merge(x,a[y].l);
		push_up(y);
		return y;
	}
}
int root;
int n,m;
void print(int now){
	if(!now) return;
	push_down(now);
	print(a[now].l);
	printf("%d ",a[now].val);
	print(a[now].r);
}
int main(){
	srand(time(0));
	n=rd(),m=rd();
	for(int i=1;i<=n;i++){
		root=merge(root,New(i));
	}
	int l,r,x,y,z;
	while(m--){
		l=rd();r=rd();
		split(root,r,x,z);split(x,l-1,x,y);
		a[y].tag^=1;
		root=merge(merge(x,y),z);
	}
	print(root);
	return 0;
}
5,[NOI2004] 郁闷的出纳员 - 洛谷(懒惰标签维护区间修改)

按照权值分裂。

操作一:新建一个节点。

操作二/三:整体修改,然后将工资低于工资下界的分裂出去,并统计其数量。

操作四:排名为\(a[root].siz-k+1\)的节点

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e5+5;
int n,Min;
struct FHQ_Treap{
	int l,r,val,lazy,siz,rnd;
}a[N];
int tot;
int New(int val){
	a[++tot].val=val;a[tot].siz=1;a[tot].rnd=rand();
	return tot;
}
void dosth(int now,int x){
	a[x].lazy+=a[now].lazy,a[x].val+=a[now].lazy;
}
void push_down(int k){
	if(!a[k].lazy) return;
	if(a[k].l) dosth(k,a[k].l);
	if(a[k].r) dosth(k,a[k].r);
	a[k].lazy=0;
}
void push_up(int k){
	a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}
	push_down(now);
	if(a[now].val<=k){
		x=now;
		split(a[now].r,k,a[x].r,y);
	}
	else{
		y=now;
		split(a[now].l,k,x,a[y].l);
	}
	push_up(now);
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(a[x].rnd<a[y].rnd){
		push_down(x); 
		a[x].r=merge(a[x].r,y);
		push_up(x);return x;
	}
	else{
		push_down(y);
		a[y].l=merge(x,a[y].l);
		push_up(y);return y;
	}
}
int find(int now,int k){
	while(1){
		push_down(now);
		if(a[a[now].l].siz>=k) now=a[now].l;
		else if(a[a[now].l].siz+1==k) return now;
		else k-=1+a[a[now].l].siz,now=a[now].r;
	}
}
int main(){
	srand(time(0));
	char opt;int k;
	int root,x,y;
	cin>>n>>Min;
	int ans=0;
	while(n--){
		cin>>opt>>k;
		if(opt=='I'){
			if(k<Min) continue;
			split(root,k-1,x,y);
			root=merge(merge(x,New(k)),y);
		}
		else if(opt=='F'){
			if(a[root].siz<k) puts("-1");
			else printf("%d\n",a[find(root,a[root].siz-k+1)].val); 
		}
		else{
			if(opt=='S') k*=-1;
			a[root].val+=k,a[root].lazy+=k;
			if(opt=='S'){
				split(root,Min-1,x,root);
				ans+=a[x].siz;
			}
		}
	}	
	printf("%d\n",ans);
	return 0;
}
6,贫穷 - 洛谷(查询前先跳到根节点从上往下下放懒惰标记)

按照排名来分裂 。

Treap 维护左儿子下标,右儿子下标,这个节点代表的字母,Treap 的随机值,该节点为根的树的大小,该节点为根的树内节点包含的字母的状压值,区间翻转的懒惰标记,该节点的父亲(没有的话为空),这个点是否被删除。

操作一:

分裂成两颗,然后和新节点合并。

操作二:

分裂成三颗,把左右两颗合并,记得给被删除的节点打上标记,后面有用。

操作三:

区间翻转,分裂成三颗,然后给对应的打上标记即可。

操作四:

首先,根据标记判断那个节点有没有被删除。

若没有被删除,就是找到下标为 \(x\) 的节点的排名。

注意,由于有区间翻转操作,需要先从 \(x\) 递归到 \(root\),从上往下依次标记下传。

void down(int k){//依次下放 
	if(a[k].fa) down(a[k].fa);
	push_down(k);
}
int find(int x){//节点x的排名 
	down(x);//因为和结构有关,所以要先下传标记. 
	int ans=a[a[x].l].siz+1;
	while(a[x].fa){
		if(a[a[x].fa].r==x) ans+=a[a[x].fa].siz-a[x].siz;
		x=a[x].fa;
	}
	return ans;
}

操作五:

查找第 \(x\) 大,记得在查找前先下传标记。

int kth(int x,int k){
	while(1){
		push_down(x);//下传标记
		if(a[a[x].l].siz>=k) x=a[x].l;
		else if(a[a[x].l].siz+1==k) return x;
		else k-=1+a[a[x].l].siz,x=a[x].r;
	}
}

操作六:

先分裂出来对应的区间。

查找其根节点的状压变量二进制下一的个数即可。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
struct node{
	int l,r,val,rnd,siz,cnt,tag,fa;
	bool flag;
}a[N<<1];
int tot;
int New(int id){
	a[++tot].rnd=rand()*rand();
	a[tot].siz=1;
	a[tot].val=id;
	a[tot].cnt=(1<<id); 
	return tot;
}
void push_up(int k){
	if(a[k].l) a[a[k].l].fa=k;
	if(a[k].r) a[a[k].r].fa=k;
	a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1;
	a[k].cnt=a[a[k].l].cnt|a[a[k].r].cnt|(1<<a[k].val);
}
void push_down(int k){
	if(!a[k].tag) return;
	if(a[k].l) a[a[k].l].tag^=1;
	if(a[k].r) a[a[k].r].tag^=1;
	swap(a[k].l,a[k].r);
	a[k].tag=0;
}
void down(int k){//依次下放 
	if(a[k].fa) down(a[k].fa);
	push_down(k);
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}
	push_down(now);
	if(a[a[now].l].siz>=k){
		y=now;split(a[now].l,k,x,a[y].l);
	}
	else{
		x=now;split(a[now].r,k-1-a[a[now].l].siz,a[x].r,y);
	}
	push_up(now);
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(a[x].rnd<a[y].rnd){
		push_down(x);a[x].r=merge(a[x].r,y);
		push_up(x);return x;
	}
	else{
		push_down(y);a[y].l=merge(x,a[y].l);
		push_up(y);return y;
	}
}
int find(int x){//节点x的排名 
	down(x);//因为和结构有关,所以要先下传标记. 
	int ans=a[a[x].l].siz+1;
	while(a[x].fa){
		if(a[a[x].fa].r==x) ans+=a[a[x].fa].siz-a[x].siz;
		x=a[x].fa;
	}
	return ans;
}
int kth(int x,int k){
	while(1){
		push_down(x);
		if(a[a[x].l].siz>=k) x=a[x].l;
		else if(a[a[x].l].siz+1==k) return x;
		else k-=1+a[a[x].l].siz,x=a[x].r;
	}
}
string s;
int n,m,root,rt1,rt2,rt3,rt4;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	srand(time(0));
	cin>>n>>m>>s;s=' '+s;
	for(int i=1;i<=n;i++){
		root=merge(root,New(s[i]-'a'));
	}
	int x,y;
	char opt,c;
	while(m--){
		cin>>opt;
		if(opt=='I'){
			cin>>x>>c;
			split(root,x,rt1,rt2);
			root=merge(merge(rt1,New(c-'a')),rt2);
		}
		if(opt=='D'){
			cin>>x;
			split(root,x,rt1,rt2);
			split(rt1,x-1,rt1,rt3);
			a[rt3].flag=1;
			root=merge(rt1,rt2);
		}
		if(opt=='R'){
			cin>>x>>y;
			split(root,y,rt1,rt3);
			split(rt1,x-1,rt1,rt2);
			a[rt2].tag^=1;
			root=merge(merge(rt1,rt2),rt3);
		} 
		if(opt=='P'){
			cin>>x;
			if(a[x].flag) cout<<"0\n";
			else cout<<find(x)<<"\n";
		}
		if(opt=='T'){
			cin>>x;
			cout<<(char)(a[kth(root,x)].val+'a')<<"\n"; 
		}
		if(opt=='Q'){
			cin>>x>>y;
			split(root,y,rt1,rt3);
			split(rt1,x-1,rt1,rt2);
			cout<<__builtin_popcount(a[rt2].cnt)<<"\n";
			root=merge(merge(rt1,rt2),rt3);
		}
	}
	return 0;
}
7,【模板】可持久化平衡树 - 洛谷

模板,直接操作即可。

注意开大空间。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int rd(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int N=5e5+5;
int n;
struct node{
	int l,r,siz,val,rnd;
}a[N*50];
int tot,root[N];
void push_up(int k){
	a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1;
}
int build(int val){
	a[++tot].siz=1;a[tot].val=val;a[tot].rnd=rand()*rand();
	return tot;
}
int New(int k){
	a[++tot]=a[k];
	return tot;
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x=y=0;
		return;
	}	
	if(a[now].val<=k){ 
		x=New(now);
		split(a[now].r,k,a[x].r,y);
		push_up(x);
	}
	else{
		y=New(now);
		split(a[now].l,k,x,a[y].l);
		push_up(y);
	}
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(a[x].rnd<a[y].rnd){
		x=New(x);
		a[x].r=merge(a[x].r,y);
		push_up(x);
		return x;
	} 
	else{
		y=New(y);
		a[y].l=merge(x,a[y].l);
		push_up(y);
		return y;
	}
}
int kth(int now,int k){
	while(1){
		if(a[a[now].l].siz>=k) now=a[now].l;
		else if(a[a[now].l].siz+1==k) return now;
		else k-=1+a[a[now].l].siz,now=a[now].r;
	}
}
int rt1,rt2,rt3;
signed main(){
//	freopen("P3835_3.in","r",stdin);
	srand(time(0));
	n=rd();
	int v,opt,x;
	for(int i=1;i<=n;i++){
		v=rd(),opt=rd(),x=rd();
		root[i]=root[v];
		if(opt==1){
			split(root[i],x,rt1,rt2);
			root[i]=merge(merge(rt1,build(x)),rt2);
		}
		else if(opt==2){
			split(root[i],x,rt1,rt3);
			split(rt1,x-1,rt1,rt2);
			rt2=merge(a[rt2].l,a[rt2].r);
			root[i]=merge(merge(rt1,rt2),rt3);
		}
		else if(opt==3){
			split(root[i],x-1,rt1,rt2);
			printf("%lld\n",a[rt1].siz+1);
			root[i]=merge(rt1,rt2);
		}
		else if(opt==4){
			printf("%lld\n",a[kth(root[i],x)].val);
		}
		else if(opt==5){
			split(root[i],x-1,rt1,rt2);
			if(!rt1)printf("%lld\n",-(1ll<<31)+1);
			else printf("%lld\n",a[kth(rt1,a[rt1].siz)].val);
			root[i]=merge(rt1,rt2);
		}
		else{
			split(root[i],x,rt1,rt2);
			if(!rt2)printf("%lld\n",(1ll<<31)-1);
			else printf("%lld\n",a[kth(rt2,1)].val);
			root[i]=merge(rt1,rt2);
		}
	}
	
	return 0;
}
8,【模板】可持久化文艺平衡树- 洛谷

模板,直接操作即可。

注意开大空间,以及标记下传中的细节。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int rd(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int N=2e5+5;
int n;
struct node{
    int l,r,siz,rnd,val,tag,sum;
}a[N*100];
int tot,root[N];
int build(int val){
    a[++tot].val=val;
    a[tot].sum=val;
    a[tot].siz=1;
    a[tot].rnd=rand();
    return tot;
} 
int New(int x){
    a[++tot]=a[x];
    return tot;
}
void push_down(int k){
    if(!a[k].tag) return;
    a[k].tag=0;
    if(a[k].l){
        a[k].l=New(a[k].l);
        a[a[k].l].tag^=1;
    }
    if(a[k].r){
        a[k].r=New(a[k].r);
        a[a[k].r].tag^=1;
    }
    swap(a[k].l,a[k].r);
}
void push_up(int k){
    a[k].sum=a[a[k].l].sum+a[a[k].r].sum+a[k].val;
    a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1;
}

void split(int now,int k,int &x,int &y){
    if(!now){
        x=y=0;
        return;
    }
    push_down(now);
    if(a[a[now].l].siz>=k){//不在右子树内 
        y=New(now);
        split(a[now].l,k,x,a[y].l);
        push_up(y);
    }
    else{
        x=New(now);
        split(a[now].r,k-a[a[now].l].siz-1,a[x].r,y);
        push_up(x);
    }
}
int merge(int x,int y){
    if(!x||!y)return x+y;
    if(a[x].rnd<a[y].rnd){
        x=New(x);push_down(x);
        a[x].r=merge(a[x].r,y);
        push_up(x);return x;
    } 
    else{
        y=New(y);push_down(y);
        a[y].l=merge(x,a[y].l);
        push_up(y);return y;
    }   
} 
int rt1,rt2,rt3;
signed main(){
    srand(time(0));
    n=rd();
    int ans=0;
    int v,opt,p,x,l,r;
    for(int i=1;i<=n;i++){
        v=rd(),opt=rd();
        root[i]=root[v];
        if(opt==1){
            p=rd()^ans,x=rd()^ans;
            split(root[i],p,rt1,rt2);
            root[i]=merge(merge(rt1,build(x)),rt2);
        }
        else if(opt==2){
            p=rd()^ans;
            split(root[i],p-1,rt1,rt3);
            split(rt3,1,rt2,rt3);
            root[i]=merge(rt1,rt3);
        }
        else if(opt==3){
            l=rd()^ans,r=rd()^ans;
            split(root[i],r,rt1,rt3);
            split(rt1,l-1,rt1,rt2);
            a[rt2].tag^=1;
            root[i]=merge(merge(rt1,rt2),rt3);
        }
        else{
            l=rd()^ans,r=rd()^ans;
            split(root[i],r,rt1,rt3);
            split(rt1,l-1,rt1,rt2);
            ans=a[rt2].sum;
            root[i]=merge(merge(rt1,rt2),rt3);
            printf("%lld\n",ans);
        }
    }
    return 0;
}
posted @ 2024-04-07 21:54  123456xwd  阅读(38)  评论(0)    收藏  举报