各种树模板(splay,线段树,可持久化线段树...)

这篇博客探讨了排序Splay树的关键点,包括保持复杂度的splay操作、if条件顺序、旋转特判等,并警告避免不必要的子树位置更改。博主分享了一种Splay在特定问题上的运行时间,并指出其常数因子较高,不适合树套树的场景。此外,还介绍了可持久化线段树的空间优化和前缀和技巧,以及树套树(树状数组结合动态开点线段树)的实现细节,强调离散化的重要性。最后,提到了不带标记的换根LCT在裸题中的应用。

这是裸的排序Splay

AC tyvj1728 普通平衡树


#include <cstdio>
#include <iostream>
#include <fstream>

#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>

typedef long long int ll;
typedef double db;

using namespace std;

struct SplayTree
{
	struct node
	{
		int v;
		int tot;
		node*s[2];
		node*f;
		
		void update()
		{
			tot=s[0]->tot + s[1]->tot +1;
		}
	};
	node*pool;
	node*nt;
	node*nil;
	node*newnode(node*f,int v)
	{
		nt->v=v;
		nt->tot=1;
		nt->s[0]=nt->s[1]=nil;
		nt->f=f;
		return nt++;
	}
	
	node*root;
	
	SplayTree(int size)
	{
		pool=new node[size+1];
		nt=pool;
		nil=newnode(NULL,-1);
		nil->tot=0;
		nil->f=nil->s[0]=nil->s[1]=nil;
		root=nil;
	}
	
	//===============================================
	
	void update(node*x)
	{
		x->tot= x->s[0]->tot + x->s[1]->tot +1;
	}
	
	void rot(node*x)
	{
		if(x==nil) return ;
		
		node*y=x->f;
		int k=(x==y->s[0]);
		
		y->s[k^1]=x->s[k];
		if(x->s[k]!=nil) x->s[k]->f=y;
		
		x->f=y->f;
		if(y==y->f->s[0]) y->f->s[0]=x;
		else if(y==y->f->s[1]) y->f->s[1]=x;
		
		y->f=x;
		x->s[k]=y;
		
		y->update();
	}
	
	void splay(node*x) { splay(x,nil); }
	void splay(node*x,node*t)
	{
		if(x==nil) return ;
		while(x->f!=t)
		{
			node*y=x->f;
			if(y->f!=t) 
			if((x==y->s[0])^(y==y->f->s[0]))
				rot(y); else rot(x);
			rot(x);
		}
		x->update();
		
		if(t==nil) root=x;
	}
	
	//=============================================
	
	void Insert(int v)
	{
		if(root==nil)
		{
			root=newnode(nil,v);
			return ;
		}
		
		node*x=root;
		node*y=x;
		while(x!=nil)
		{
			y=x;
			if(v<x->v) x=x->s[0];
			else x=x->s[1];
		}
		
		int k=!(v<y->v);
		y->s[k]=newnode(y,v);
		splay(y->s[k]);
	}
	
	
	node*Find(int v)
	{
		node*x=root;
		node*y=x;
		node*r=nil;
		while(x!=nil)
		{
			y=x;
			if(x->v==v) r=x;
			if(v<=x->v) x=x->s[0];
			else x=x->s[1];
		}
		splay(y);
		return r;
	}
	
	node* FindRank(int k)
	{
		node*x=root;
		node*y=x;
		while(x!=nil)
		{
			y=x;
			if(k==x->s[0]->tot+1) break;
			
			if(k<x->s[0]->tot+1)
				x=x->s[0];
			else
			{
				k-=x->s[0]->tot+1;
				x=x->s[1];
			}
		}
		splay(y);
		return x;
	}
	
	int GetRank(node*x)
	{
		splay(x);
		return x->s[0]->tot+1;
	}
	
	int GetRevRank(node*x)
	{
		splay(x);
		return x->s[1]->tot+1;
	}
	
	node*Delete(node*x)
	{
		int k=GetRank(x);
		node*L=FindRank(k-1);
		node*R=FindRank(k+1);
		
		splay(L);
		splay(R,L);
		
		if(L==nil && R==nil) root=nil;
		else if(R==nil) L->s[1]=nil;
		else R->s[0]=nil;
		
		if(R!=nil) update(R);
		if(L!=nil) update(L);
		
		return x;
	}
	
	node*prefix(int v)
	{
		node*x=root;
		node*y=x;
		node*r=nil;
		while(x!=nil)
		{
			y=x;
			if(x->v<v) r=x;
			if(v<=x->v) x=x->s[0];
			else x=x->s[1];
		}
		splay(y);
		return r;
	}
	
	node*suffix(int v)
	{
		node*x=root;
		node*y=x;
		node*r=nil;
		while(x!=nil)
		{
			y=x;
			if(x->v>v) r=x;
			if(v<x->v) x=x->s[0];
			else x=x->s[1];
		}
		splay(y);
		return r;
	}
	
	
	
	
	//===========================================
	void output() { output(root); printf("%s\n",root==nil ? "empty tree!" : ""); }
	void output(node*x)
	{
		if(x==nil)return ;
		output(x->s[0]);
		printf("%d ",x->v);
		output(x->s[1]);
	}
	
	void test() { test(root); printf("%s\n",root==nil ? "empty tree!" : ""); }
	void test(node*x)
	{
		if(x==nil)return ;
		test(x->s[0]);
		printf("%p [ v:%d f:%p L:%p R:%p tot:%d ] \n",x,x->v,x->f,x->s[0],x->s[1],x->tot);
		test(x->s[1]);
	}
	
};


int n;

int main()
{
	scanf("%d",&n);
	SplayTree st(n);
	
	for(int i=0;i<n;i++)
	{
		int c;
		scanf("%d",&c);
		switch(c)
		{
			case 1: //Insert
				scanf("%d",&c);
				st.Insert(c);
			break;
			case 2: //Delete
				scanf("%d",&c);
				st.Delete(st.Find(c));
			break;
			case 3: //Rank
				scanf("%d",&c);
				printf("%d\n",st.GetRank(st.Find(c)));
			break;
			case 4: //FindRank
				scanf("%d",&c);
				printf("%d\n",st.FindRank(c)->v);
			break;
			case 5: //prefix
				scanf("%d",&c);
				printf("%d\n",st.prefix(c)->v);
			break;
			case 6: //suffix
			 	scanf("%d",&c);
			 	printf("%d\n",st.suffix(c)->v);
			break;
			case 7: //test
				st.test();
			break;
			default: break; 
		} 
	}

	return 0;
}




要点1.别忘了有事没事splay一下保证复杂度.....

要点2.各种if的顺序别搞混了!有些if是不能合并的.

要点3.记住splay前的特判.如果要单独使用rotate就给rotate也加特判.

要点4.不要有事没事就更改某些子树的位置!比如在delete的时候,提x作根,然后找到右子树最左边的节点后,合并左右两颗子树,这是会超时的!


代码超长...神犇勿拍....

还有优化的余地.....

另外我发现好多神奔居然除了insert和delete,都不splay....然后我写就狂T........ORZ..

还是不怎么会写二叉查找树的常规....比如FindRank之类....多练就能熟练吧..

这个Splay在TYVJ的T1728上跑了大约400ms. 数据范围是操作数=10W. 鉴于以前写单旋splay能差不多这个时间过,数据应该是随机的....

也就是说...这个Splay的常数大概有20+....好恐怖...我线段树好歹也只有5到6........

也就是说不能用这个模板做树套树之类了TAT

还是找个时间改进吧....


//=================================================================================

可持久化线段树

AC VIJOS 1081 野生动物园

一道非常裸的区间k大

const int INF=(1<<30)-1;

struct node
{
	int t;
	node*l,*r;
	node(){ t=0; l=r=NULL;  }
	
	void update()
	{ t=l->t+r->t; }
}pool[4000000];
int nt;
node*newnode()
{ return &pool[nt++]; }

node*nil;

node*root[100050];

void SegmentTreeInit(int size)
{
	nil=newnode();
	nil->l=nil->r=nil;
	nil->t=0;
	for(int i=0;i<=size;i++)
	root[i]=nil;
}

int cp;
node*Change(node*x,node*y,int l,int r)
{
	if(cp<l || r<cp) return y;
	x=newnode();
	if(l==r) { x->t=1+y->t; return x; }
	int mid=(l+r)>>1;
	x->l=Change(x->l,y->l,l,mid);
	x->r=Change(x->r,y->r,mid+1,r);
	x->update();
	return x;
}
void Change(int i,int j,int pos)
{ cp=pos; root[i]=Change(nil,root[j],0,INF); }

int Query(int ql,int qr,int k)
{
	node*x=root[ql],*y=root[qr];
	int l=0,r=INF;
	while(l!=r)
	{
		int mid=(l+r)>>1;
		if( k<= x->l->t - y->l->t )
			 r=mid,x=x->l,y=y->l;
		else
		{
			k-=x->l->t-y->l->t;
			l=mid+1,x=x->r,y=y->r;
		}
	}
	return l;
}



int n;



int main()
{
	
	int q;
	scanf("%d",&n);
	scanf("%d",&q);
	
	SegmentTreeInit(n);
	
	
	for(int i=0;i<n;i++)
	{
		int c;
		scanf("%d",&c);
		cp=c;
		root[i+1]=Change(root[i+1],root[i],0,INF);
	}
	
	
	for(int i=0;i<q;i++)
	{
		int a,b,k;
		scanf("%d%d%d",&a,&b,&k);
		printf("%d\n",Query(b,a-1,k));
	}
	
	return 0;
}

要点1.使用nil节点可以省一点代码

要点2.千万注意空间开销.一般为nlogv级别,数组经常开上百万(懒得写离散化系列)

要点3.注意前缀和的写法. tree[R]-tree[L-1]. 这就要求root[0]=nil.

要点4.智商捉急表示普通查找操作总是写错...splay也一样.....思考...思考......写之前一定要想好....





//====================================================================================

第一次写树套树

AC VIJOS 1665

树状数组 套 动态开点的权值线段树

题目就是裸的带修改区间K大

写了一个多小时...调了大概....一个半小时?

树状数组怎么写都快忘记了........

由于懒得离散化......所以.....开了一个巨大的数组.......

VJ的内存限制不错....先把数组从400W改到800W...还是RE..怒而改到1.3kW...AC了.....

看了看空间消耗.....160M.....

这告诉我们千万不要忽视离散化的力量!  千万不要忽视常数空间!

但是我还是很懒=w=能不写离散化就不写离散化=w=

好吧......

下面是代码.....

附带一大版的文件头以及调试信息2333

#include <cstdio>
#include <fstream>
#include <iostream>
 
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
 
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <list>
 
typedef unsigned int uint;
typedef long long int ll;
typedef unsigned long long int ull;
typedef double db;
 
using namespace std;
 
inline int getint()
{
    int res=0;
    char c=getchar();
    bool mi=false;
    while(c<'0' || c>'9') mi=(c=='-'),c=getchar();
    while('0'<=c && c<='9') res=res*10+c-'0',c=getchar();
    return mi ? -res : res;
}
inline ll getll()
{
    ll res=0;
    char c=getchar();
    bool mi=false;
    while(c<'0' || c>'9') mi=(c=='-'),c=getchar();
    while('0'<=c && c<='9') res=res*10+c-'0',c=getchar();
    return mi ? -res : res;
}
 
db eps=1e-18;
inline bool feq(db a,db b)
{ return fabs(a-b)<eps; }

inline int avg(const int a,const int b)
{ return a+((b-a)>>1); }

//==============================================================================
//==============================================================================
//==============================================================================
//==============================================================================

const int INF=(1<<30)-1;

int n;
struct node*nil;
struct node
{
	int v; //total
	node*l,*r;
	void update()
	{ v=l->v+r->v; }
}pool[13000000];
node*nt=pool;
node*newnode()
{
	nt->l=nt->r=nil;
	nt->v=0;
	return nt++;
}

int cp,cv;

//Sub segment trees
struct SegmentTree
{
	node*root;
	
	node*Change(node*x,int l=0,int r=INF)
	{
		if(cp<l || r<cp) return x;
		if(x==nil) x=newnode();
		if(l==r) { x->v+=cv; return x; }
		int mid=avg(l,r);
		x->l=Change(x->l,l,mid);
		x->r=Change(x->r,mid+1,r);
		x->update();
		return x;
	}
	
	void Set(int p,int v)
	{
		if(root<pool) root=nil;
		cp=p;
		cv=v;
		root=Change(root);
	}
};

//original segment tree
//performed as tree array

#define bt(x) (x&-x)

int a[1000000]; //this array must be stay here....
SegmentTree t[1000000];
void Change(int p,int s,int v) //location of point, value of point, delete or add in.
{ for(int i=p;i<=n;i+=bt(i)) t[i].Set(s,v); }

node*c[1000];
int adt,ct;

int Query(int l,int r,int k) //find the element which is rank k.
{
	adt=0;
	
	for(int i=r;i>0;i-=bt(i))
	c[adt++]=t[i].root;
	
	ct=adt;
	for(int i=l-1;i>0;i-=bt(i))
	c[ct++]=t[i].root;
	
	//we perform add when i<adt, and than dec when adt<=i<ct
	
	
	l=0,r=INF;
	while(l!=r)
	{
		//for(int i=0;i<ct;i++)
		//cout<<c[i]<<' '; cout<<endl; cout<<l<<' '<<r<<endl; cout<<endl;
	
		int mid=avg(l,r);
		int clv=0; //current node's left node count.
		
		for(int i=0;i<adt;i++)
		clv+=c[i]->l->v;
		for(int i=adt;i<ct;i++)
		clv-=c[i]->l->v;
		
		if(k<=clv) //the element we want find is on the left block
		{
			for(int i=0;i<ct;i++)
				c[i]=c[i]->l;
			r=mid;
		}
		else
		{
			for(int i=0;i<ct;i++)
				c[i]=c[i]->r;
			k-=clv;
			l=mid+1;
		}
	}
	
	return l;
}

int q;

int main()
{
	nil=newnode();
	nil->l=nil->r=nil;
	nil->v=0;
	
	n=getint();
	q=getint();
	for(int i=0;i<n;i++)
	Change(i+1,a[i+1]=getint(),1);
	
	for(int i=0;i<q;i++)
	{
		char c[5];
		scanf("%s",c);
		if(c[0]=='C')
		{
			int i=getint();
			int v=getint();
			Change(i,a[i],-1);
			Change(i,a[i]=v,1);
		}
		else
		{
			int i=getint();
			int j=getint();
			int k=getint();
			printf("%d\n",Query(i,j,k));
		}
	}
	
	return 0;
}

需要注意的地方:

1.树状数组什么的一级结构别写错了啊啊啊啊啊啊

2.既然是动态开点(即便离散化了也给我动态!绝对不要写静态的树套在另外的树里面!)....

那么,我们只需要记录每棵树的根节点就好了.其它节点在访问的时候会碰到.

3.嗯....(结构相同的)线段树是可加的.......所以不要再去写二分,直接在加起来的那棵树上隐式二分即可.详见代码.可以降低一个log的复杂度.

4.二分的界,以及二分后的操作(k-=...)一定要考虑清楚.

<5.不知道为什么, 用@iwtwiioi在某些地方的AC代码交到VJ,TLE了...就AC了我第一次提交没有RE的那两个范围较小的点...... http://www.cnblogs.com/iwtwiioi/p/3929957.html>

<满满的成就感><我估计马上会被吐槽>







//====================================================================================

不带其余任何标记的,带换根的LCT.

AC BZOJ2049 一道非常裸的LCT

#include <cstdio>
#include <fstream>
#include <iostream>
 
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
 
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <list>
 
typedef unsigned int uint;
typedef long long int ll;
typedef unsigned long long int ull;
typedef double db;
 
using namespace std;
 
inline int getint()
{
    int res=0;
    char c=getchar();
    bool mi=false;
    while(c<'0' || c>'9') mi=(c=='-'),c=getchar();
    while('0'<=c && c<='9') res=res*10+c-'0',c=getchar();
    return mi ? -res : res;
}
inline ll getll()
{
    ll res=0;
    char c=getchar();
    bool mi=false;
    while(c<'0' || c>'9') mi=(c=='-'),c=getchar();
    while('0'<=c && c<='9') res=res*10+c-'0',c=getchar();
    return mi ? -res : res;
}
 
db eps=1e-18;
inline bool feq(db a,db b)
{ return fabs(a-b)<eps; }

template<typename Type>
inline Type avg(const Type a,const Type b)
{ return a+((b-a)/2); }

//==============================================================================
//==============================================================================
//==============================================================================
//==============================================================================





struct node* nil;

struct node
{
	bool rev;
	node*s[2],*f;

	bool root() 
	{ return this!=f->s[0] && this!=f->s[1]; }
	
	void pushtag()
	{
		if(rev)
		{
			s[0]->rev^=1;
			s[1]->rev^=1;
			swap(s[0],s[1]);
			rev=false;
		}
	}
	
};


struct LinkCutTree
{
	node*nt;
	LinkCutTree(int size)
	{
		nt=new node[size];
		for(int i=0;i<size;i++)
		{
			nt[i].s[0]=nt[i].s[1]=nt[i].f=nil;
			nt[i].rev=false;
		}
	}
	
	void cleartag(node*x)
	{ if(!x->root()) cleartag(x->f); x->pushtag(); }
	
	node*operator[](int k)
	{ return nt+k; }
	
	void rot(node*x)
	{
		if(x->root()) return ;
		node*y=x->f;
		bool k=(x==y->s[0]);
		
		y->s[!k]=x->s[k];
		if(x->s[k]!=nil) x->s[k]->f=y;
		
		x->f=y->f;
		if(y==y->f->s[0]) y->f->s[0]=x;
		else if(y==y->f->s[1]) y->f->s[1]=x;
		
		y->f=x;
		x->s[k]=y;
	}
	
	void splay(node*x)
	{
		cleartag(x);
		while(!x->root())
		{
			node*y=x->f;
			if(!y->root())
			{
				if((x==y->s[0])^(y==y->f->s[0]))
				rot(y); else rot(x);
			}
			rot(x);
		}
	}
	
	node*access(node*x)
	{
		node*y=nil;
		node*t=x;
		while(t!=nil)
		{
			splay(t);
			t->s[0]=y;
			y=t;
			t=t->f;
		}
		splay(x);
		return x;
	}
	
	node*FindRoot(node*x)
	{
		access(x);
		while(x->s[1]!=nil) x=x->s[1];
		return x;
	}
	
	node*SetRoot(node*x)
	{
		access(x)->rev^=1;
		return x;
	}
	
	void Link(node*x,node*y)
	{
		SetRoot(x)->f=y;
	}
	
	void Cut(node*x,node*y)
	{
		SetRoot(x);
		access(y);
		y->s[1]->f=nil;
		y->s[1]=nil;
	}
	
	void output(int i)
	{ cout<<i<<' '<<&nt[i]<<' '<<nt[i].s[0]<<' '<<nt[i].s[1]<<' '<<nt[i].f<<endl; }
};

int n,m;

int main()
{
	nil=new node;
	nil->s[0]=nil->s[1]=nil->f=nil;
	
	n=getint();
	m=getint();
	
	LinkCutTree t(n);
	
	for(int i=0;i<m;i++)
	{
		char c[20];
		scanf("%s",c);
		if(c[0]=='C') //Link
		{
			t.Link(t[getint()-1],t[getint()-1]);
		}
		else if(c[0]=='D') //Cut
		{
			t.Cut(t[getint()-1],t[getint()-1]);
		}
		else if(c[0]=='Q') //Query
		{
			if(t.FindRoot(t[getint()-1])==t.FindRoot(t[getint()-1])) printf("Yes\n");
			else printf("No\n");
		}
	}
	
	return 0;
}


很奇怪,Link,Cut,FindRoot和SetRoot这些函数换一种写法就各种TLE/RTE,还有cleartag()也是一样,不知道为什么.....TAT
可持久化splay是一种数据结构,它是对splay进行修改查询的一种扩展。在传统的splay中,对的修改操作会破坏原有的结构,而可持久化splay则允许我们对进行修改、查询,并且可以保存修改后的每个版本的结构。 在可持久化splay中,我们不会直接对原进行修改,而是通过复制每个节点来创建新的版本。这样,每个版本都可以独立地修改查询,保留了原有版本的结构状态。每个节点保存了其左子右子的引用,使得可以在不破坏原有版本的情况下进行修改查询。 为了实现可持久化splay,我们可以使用一些技巧,比如引用中提到的哨兵节点假的父节点孩子节点。这些技巧可以帮助我们处理根节点的旋转其他操作。 此外,可持久化splay还可以与其他数据结构相结合,比如引用中提到的可持久化线段树。这种结合可以帮助我们解决更复杂的问题,比如区间修改区间查询等。 对于可持久化splay的学习过程,可以按照以下步骤进行: 1. 理解splay的基本原理操作,包括旋转、插入、删除查找等。 2. 学习如何构建可持久化splay,包括复制节点、更新版本保存历史版本等。 3. 掌握可持久化splay的常见应用场景,比如区间修改区间查询等。 4. 深入了解与可持久化splay相关的其他数据结构算法,比如可持久化线段树等。 在解决问题时,可以使用二分法来确定答案,一般称为二分答案。通过对答案进行二分,然后对每个答案进行检查,以确定最终的结果。这种方法可以应用于很多问题,比如引用中提到的在线询问问题。 综上所述,可持久化splay是一种对splay进行修改查询的扩展,可以通过复制节点来创建新的版本,并且可以与其他数据结构相结合解决更复杂的问题。学习过程中可以按照一定的步骤进行,并且可以使用二分法来解决一些特定的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [[学习笔记]FHQ-Treap及其可持久化](https://blog.csdn.net/weixin_34283445/article/details/93207491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [可持久化数据结构学习笔记](https://blog.csdn.net/weixin_30376083/article/details/99902410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值