[学习笔记]splay

结构体版

\(c[0]\)左儿子,\(c[1]\)右儿子,\(f\)父亲,\(siz\)\(u\)为根的子树大小,\(val\)为这个点表示的值,\(cnt\)为这个值的个数,\(rt\)为根.

struct Splay{
	int c[2],f,siz,cnt,val;
}tr[N];
int rt;

一些简单函数

\(son(u)\)判断\(u\)为左/右儿子.
\(recnt(u)\)重新计算以\(u\)为根的子树大小.
\(ins_p(f,u,c)\)插入\(u\),为\(f\)\(c\)儿子.

inline bool son(int u){
	return tr[tr[u].f].c[1]==u;
}
inline void recnt(int u){
	tr[u].siz=tr[u].cnt+tr[tr[u].c[0]].siz+tr[tr[u].c[1]].siz; 
}
inline void ins_p(int f,int u,int c){
	tr[u].f=f;tr[f].c[c]=u;
}

旋转

这是\(splay\)的核心代码.
以下图为例:

将紫色节点旋转到粉色节点.
将紫色节点取代粉色节点的位置;
如果紫色节点为粉色节点右儿子,紫色节点的左儿子代替紫色节点原位置,粉色节点为紫色节点左儿子;否则紫色节点的右儿子代替紫色节点原位置,粉色节点为紫色节点右儿子.
旋转后:

\(rotate(u)\)就是将\(u\)旋转到它的父亲.
\(splay(u)\)则是将\(u\)旋转到根:

  • 如果\(u\)的父亲为根,直接旋转\(u\).
  • 如果\(u,fa\)同为左/右儿子,先转\(fa\),再转\(u\).
  • 如果\(u,fa\)不同为左/右儿子,转\(u\),再转\(u\).
inline void rotate(int u){
	int f=tr[u].f;bool c=son(u);
	ins_p(tr[f].f,u,son(f));
	ins_p(f,tr[u].c[c^1],c);
	ins_p(u,f,c^1);
	recnt(f);recnt(u);
	if(!tr[u].f) rt=u; 
}
inline void splay(int u){
	while(tr[u].f){
		if(!tr[tr[u].f].f) rotate(u);
		else{
			if(son(u)==son(tr[u].f)){
				rotate(tr[u].f);rotate(u);
			}
			else{
				rotate(u);rotate(u);
			}
		}
	}
} 

插入一个值

如果存在,那个点的\(cnt+1\),否则新建一个点,把它转到根.

inline int find(int x){
	int u=rt;
	while(tr[u].val!=x&&u){
		if(x<tr[u].val)
			u=tr[u].c[0];
		else u=tr[u].c[1];
	}
	return u;
}
inline void insert(int x){
	int u=find(x),f=0;
	if(u){
		++tr[u].cnt;
		while(u){
			recnt(u);u=tr[u].f;
		}
		return;
	}
	u=rt;
	while(u){
		f=u;
		if(x<tr[u].val) 
			u=tr[u].c[0];
		else u=tr[u].c[1];
	}
	tr[++cnt].val=x;
	tr[cnt].siz=tr[cnt].cnt=1;
	if(!f){
		rt=cnt;recnt(rt);return;
	}
	if(x<tr[f].val)
		ins_p(f,cnt,0);
	else ins_p(f,cnt,1);
	splay(cnt);
}

删除一个点

\(near(u,c)\)求出点\(u\)的前驱和后继(即小于它的数中最大的和大于它的数中最小的,\(c=0\):前驱;\(c=1\):后继).
\(splay\)前驱,\(splay\)后继,后继变为根节点,前驱变为根节点的左儿子,\(u\)变为前驱的右子树,删去即可.

inline int near(int u,int c){
	if(tr[u].c[c]){
		u=tr[u].c[c];c^=1;
		while(tr[u].c[c])
			u=tr[u].c[c];
		return u;
	}
	while(son(u)==c){
		if(u==rt) return 0;
		u=tr[u].f;
	}
	return tr[u].f;
}
inline void clear(int u){
	tr[tr[u].f].c[son(u)]=0;
	recnt(tr[u].f);
	tr[u].c[0]=tr[u].c[1]=tr[u].f=tr[u].siz=tr[u].cnt=tr[u].val=0;
}
inline void del(int u){
	int lst=near(u,0),nxt=near(u,1);
	if(!lst||!nxt){
		splay(u);
		if(lst){
			rt=tr[u].c[0];
			tr[tr[u].c[0]].f=0;
		}
		else{
			rt=tr[u].c[1];
			tr[tr[u].c[1]].f=0;
		}
		return;
	}
	splay(lst);splay(nxt);
	rotate(lst);clear(u); 
}

数组版

\(tr[u][0]\)左儿子,\(tr[u][1]\)右儿子,\(fa[u]\)父亲,\(sz[u]\)\(u\)为根的子树大小,\(val[u]\)为这个点表示的值,\(ct[u]\)为这个值的个数,\(rt\)为根.

int tr[N][2],fa[N],sz[N],ct[N],val[N],rt;

一些简单函数

\(son(u)\)判断\(u\)为左/右儿子.
\(recnt(u)\)重新计算以\(u\)为根的子树大小.
\(ins_p(f,u,c)\)插入\(u\),为\(f\)\(c\)儿子.

inline bool son(int u){
    return tr[fa[u]][1]==u;
}
inline void recnt(int u){
    sz[u]=ct[u]+sz[tr[u][0]]+sz[tr[u][1]];
}
inline void ins_p(int f,int u,int c){
    fa[u]=f;tr[f][c]=u;
}

旋转

这是\(splay\)的核心代码.
以下图为例:

将紫色节点旋转到粉色节点.
将紫色节点取代粉色节点的位置;
如果紫色节点为粉色节点右儿子,紫色节点的左儿子代替紫色节点原位置,粉色节点为紫色节点左儿子;否则紫色节点的右儿子代替紫色节点原位置,粉色节点为紫色节点右儿子.
旋转后:

\(rotate(u)\)就是将\(u\)旋转到它的父亲.
\(splay(u)\)则是将\(u\)旋转到根:

  • 如果\(u\)的父亲为根,直接旋转\(u\).
  • 如果\(u,fa\)同为左/右儿子,先转\(fa\),再转\(u\).
  • 如果\(u,fa\)不同为左/右儿子,转\(u\),再转\(u\).
inline void rotate(int u){
    int f=fa[u];bool c=son(u);
    ins_p(fa[f],u,son(f));
    ins_p(f,tr[u][c^1],c);
    ins_p(u,f,c^1);
    recnt(f);recnt(u);
    if(!fa[u]) rt=u;
}
inline void splay(int u){
    while(fa[u]){
        if(!fa[fa[u]]) rotate(u);
        else if(son(u)==son(fa[u])){
            rotate(fa[u]);rotate(u);
        }
        else{
            rotate(u);rotate(u);
        }
    }
}

插入一个值

如果存在,那个点的\(cnt+1\),否则新建一个点,把它转到根.

inline int find(int x){
    int u=rt;
    while(x!=val(u)&&u)
        if(x<val[u])
            u=tr[u][0];
        else u=tr[u][1];
    return u;
}
inline void insert(int x){
    int u=find(x);
    if(u){
        ++ct[u];
        while(u){
            recnt(u);u=fa[u];
        }
        return;
    }
    int f=0;u=rt;
    while(u){
        f=u;
        if(x<val[u])
            u=tr[u][0];
        else u=tr[u][1];
    }
    val[++cnt]=x;sz[cnt]=ct[cnt]=1;
    if(!f){
        rt=cnt;recnt(cnt);return;
    }
    if(x<val[u]) ins_p(f,cnt,0);
    else ins_p(f,cnt,1);
    splay(cnt);
}

删除一个点

\(near(u,c)\)求出点\(u\)的前驱和后继(即小于它的数中最大的和大于它的数中最小的,\(c=0\):前驱;\(c=1\):后继).
\(splay\)前驱,\(splay\)后继,后继变为根节点,前驱变为根节点的左儿子,\(u\)变为前驱的右子树,删去即可.

inline int near(int u,int c){
	if(tr[u][c]){
		u=tr[u][c];c^=1;
		while(tr[u][c])
			u=tr[u][c];
		return u;
	}
	while(son(u)==c){
		if(u==rt) return 0;
		u=fa[u];
	}
	return fa[u];
}
inline void clear(int u){
	tr[fa[u]][son(u)]=0;recnt(fa[u]);
	tr[u][0]=tr[u][1]=fa[u]=sz[u]=ct[u]=val[u]=0;
}
inline void del(int u){
	int lst=near(u,0),nxt=near(u,1);
	if(!lst||!nxt){
		splay(u);
		if(lst){
			rt=tr[u][0];fa[tr[u][0]]=0;
		}
		else{
			rt=tr[u][1];fa[tr[u][1]]=0;
		}
		return;
	}
	splay(lst);splay(nxt);
	rotate(lst);clear(u); 
}

2017-03-28 23:12:58

posted @ 2021-11-26 20:45  Aireen_Ye  阅读(51)  评论(0编辑  收藏  举报
底部 顶部 留言板 归档 标签
Der Erfolg kommt nicht zu dir, du musst auf den Erfolg zugehen.