本来老师以为我们学过Splay,今天讲LCT,结果我们没学过,于是… …
听了会儿课,有点迷,还是自学写篇博客8。
才开始学习Splay,可能有些瑕疵,望指出。
假设要对一个二叉搜索树执行一系列查找操作,为了使得总时间最小,那么被查找频率高的节点自然就要放在靠近
根的位置。于是想到一个简单的设计方案,在每次查找之后对树进行重构,把被查找的条目搬到离树根近一点的位置。
顺着这个思路,Splay诞生了。
Splay是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列旋转把该节点搬移到
树根,同时使得该条路径上的点尽量靠近树根。
对于线段树或树状数组,维护的下标区间都是不变的,利用这个性质,可以对其二分建立树。但是在支持破坏元素
顺序的操作后,必定维护的下标区间会发生改变,间接导致建立树的形态不同,因此在原序列为最底层的基础上,每
层二分建树的方法是不可行的。基础数据结构都只支持不改变元素顺序的操作。也就是说,这些数据结构都不支持插
入或删除元素,或是破坏元素顺序的操作。
如果我们需要维护序列最值与区间和,并要求在线段树的基础操作外支持插入、删除、翻转区间、分裂及合并的操
作,该怎么办呢?那么就需要用Splay了。
我们知道,在一些情况下,二叉搜索树的时间复杂度会由 O ( log n ) O(\log n) O(logn)变为 O ( n ) O(n) O(n)(也就是树形退化为单链)。因此,我们要尽量维护二叉查找树的平衡,使树的期望高度为 log n \log n logn。
而维护这样的平衡的树据结构,我们就叫做平衡树。Splay就是其中的一种。
Splay最重要的操作是伸展操作(splaying)。伸展操作就是将一个节点向上逐步旋转(Rotate)到指定节点(一般为根节点),不改变其二叉搜索树的性质。将高频搜索节点尽量靠近根节点,就能够减少搜索的访问次数。
对于节点 x x x ,如果 x x x 点的父亲 p p p 点就为根节点的话,那么 x x x 向上转一次就到根了。
如果 x x x 为 p p p 的左儿子则右旋(Zig),反之左旋(Zag)。
给出一个右旋的例子(图片来源:知乎):
把 x x x 点转到根,把 x x x 和左子树保留, x x x 的右子树剥离出来,把 p p p 点插入 x x x 的右子树,再把 x x x 的右子树插到 p p p 的左子树。
但是,这样的单旋是很可能会T的。如果把一个很小的值转上去,那么这个点的左子树会很小,右子树会很大。在这样的极端情况下,Splay的形态会退化成链,时间复杂度又是 O ( n ) O(n) O(n)的了,就失去了平衡树的意义。这个时候,我们就要分情况讨论,进行双旋。
情况一:
对于点 x x x , x x x 的父节点 p p p , p p p 的父节点 q q q ,若 x x x、 p p p 和 p p p、 q q q的对应左右关系相同,即三点为一条直线。则先旋转 p p p,再旋转 x x x(为什么不是先 x x x 再 p p p 呢?是为了之后引入势进行复杂度分析。可以发现,这样的旋转是可逆的)。那么,这样的双旋就对应了 Zig-Zig 或 Zag-Zag
情况二:
对于点 x x x , x x x 的父节点 p p p , p p p 的父节点 q q q ,若 x x x、 p p p 和 p p p、 q q q的对应左右关系相同,即三点为一条折线。那么就将 x x x 旋转两次,对应为 Zig-Zag 或 Zag-Zig
Splay在伸展操作后不改变中序遍历序,即其对应的序列。维护信息自底向上维护,每个结点除了维护该结点对应序列元素自身的信息外,还需要维护子树对应序列上区间的信息并。每次伸展操作开始前下传标记,每次旋转操作结束后更新结点信息(类似于线段树)。
最基本的信息是子树大小(size,子树结点个数,子树大小用于确定序列第 k k k 位位置)、左右子结点编号以及父结点编号。还有一些根据题意维护的变量。
采用线段树的建树方法。时间复杂度为 O ( n ) O(n) O(n),树高为 log n \log n logn
咕咕咕
很好想,就不说了
LL GetKth(LL x)
{
LL Now = Root;
while ( 1 )
{
if (Tree[Now].Son[0] && x <= Tree[Tree[Now].Son[0]].Size)
Now = Tree[Now].Son[0];
else if (x > Tree[Tree[Now].Son[0]].Size + Tree[Now].Cnt)
{
x -= (Tree[Tree[Now].Son[0]].Size + Tree[Now].Cnt);
Now = Tree[Now].Son[1];
}
else return Now;
}
}
单点插入(在 k k k号元素后插入新元素):
将第 k k k号点右子树插入该点,再将该点插入 k k k号点即可。
void Insert(LL x)
{
LL Now = Root, Pos = 0;
while (Now && Tree[Now].Val != x)
{
Pos = Now;
Now = Tree[Now].Son[x > Tree[Now].Val];
}
if ( Now )
Tree[Now].Cnt ++;
else
{
Now = ++ tot;
if ( Pos )
Tree[Pos].Son[x > Tree[Pos].Val] = Now;
Tree[Now].Son[0] = Tree[Now].Son[1] = 0;
Tree[Now].Fa = Pos;
Tree[Now].Val = x;
Tree[Now].Size = Tree[Now].Cnt = 1;
}
Splay(Now, 0); // 将它旋到根
}
void Delete(LL x)
{
LL Over = GetPre( x ), Next = GetBack( x );
Splay(Over, 0);
Splay(Next, Over);
LL Del = Tree[Next].Son[0];
if (Tree[Del].Cnt > 1)
{
Tree[Del].Cnt --;
Splay(Del, 0);
}
else Tree[Next].Son[0] = 0;
}
区间插入(插入区间):
对于区间插入,利用伸展的性质。先将插入位置左侧元素伸展至根,再将插入位置的右侧元素伸展至根的右子结点。完成这样的伸展后,根结点的右结点即为要插入的点,在它的左子树插入即可。
咕咕咕
利用伸展操作,我们可以实现快速分裂合并。
咕咕咕
每个结点除了自身对应序列元素的信息,还需要维护的是,以该结点为根结点的子树对应的区间的信息并。在伸展操作前后的更新信息与标记下传,都保证了信息的准确。查询信息,只需要像区间插入一样,将子序列对应的子树旋转至根结点的右子结点的左子树,并对其进行操作即可。添加标记或修改信息亦同理。以子树大小为例,每次更新信息时,重置该结点的子树大小为1,并累加其左右子结点的子树大小。
咕咕咕
添加一个翻转的Lazy,在下放时交换左右结点即可。
咕咕咕
区间最值只需要维护每个结点其对应的区间的最值。查询时按照区间信息维护中提及的方法旋转并查询即可。区间求和亦同理。区间加法与区间赋值亦同理,但请注意新标记对当前结点其他信息的影响。与线段树的维护类似。
咕咕咕
整合一下:
#include
#include
#include
#include
#include
#define MAXN 100005
#define LL long long
#define Int register int
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
inline void read(LL &x)
{
x = 0;
LL f = 1;
char s = getchar();
while (s < '0' || s > '9')
{
if (s == '-')
f = -1;
s = getchar();
}
while (s >= '0' && s <= '9')
{
x = (x << 3) + (x << 1) + (s ^ 48);
s = getchar();
}
x *= f;
}
struct SPlayer
{
LL Root, tot;
struct SplayTree
{
LL Fa, Son[2], Cnt, Val, Size, Maxx;
}Tree[MAXN << 2];
bool ZY(LL x)
{
return (Tree[Tree[x].Fa].Son[1] == x);
}
void Update(LL x)
{
Tree[x].Size = Tree[x].Cnt + Tree[Tree[x].Son[0]].Size + Tree[Tree[x].Son[1]].Size;
}
void Rotate(LL x) // 将 x 旋转到 y
{
LL y = Tree[x].Fa;
LL z = Tree[y].Fa;
bool Fx = ZY( x );
LL w = Tree[x].Son[Fx ^ 1];
Tree[y].Son[Fx] = w;
Tree[w].Fa = y;
Tree[z].Son[ZY(y)] = x;
Tree[x].Fa = z;
Tree[x].Son[Fx ^ 1] = y;
Tree[y].Fa = x;
Update( y );
Update( x );
}
void Splay(LL x,LL Goal) // 将 x 旋到 Gola
{
while (Tree[x].Fa != Goal)
{
LL y = Tree[x].Fa;
LL z = Tree[y].Fa;
if (z != Goal) // 双旋
{
if (ZY(x) == ZY( y )) // 直线
Rotate( y );
else Rotate( x ); // 折线
}
Rotate( x );
}
if (! Goal)
Root = x;
}
void Insert(LL x)
{
LL Now = Root, Pos = 0;
while (Now && Tree[Now].Val != x)
{
Pos = Now;
Now = Tree[Now].Son[x > Tree[Now].Val];
}
if ( Now )
Tree[Now].Cnt ++;
else
{
Now = ++ tot;
if ( Pos )
Tree[Pos].Son[x > Tree[Pos].Val] = Now;
Tree[Now].Son[0] = Tree[Now].Son[1] = 0;
Tree[Now].Fa = Pos;
Tree[Now].Val = x;
Tree[Now].Size = Tree[Now].Cnt = 1;
}
Splay(Now, 0); // 将它旋到根
}
void Find(LL x) // 找到 x值的位置,将它旋到根
{
LL Now = Root;
while (Tree[Now].Son[x > Tree[Now].Val] && x != Tree[Now].Val)
Now = Tree[Now].Son[x > Tree[Now].Val];
Splay(Now, 0);
}
LL GetKth(LL x)
{
LL Now = Root;
while ( 1 )
{
if (Tree[Now].Son[0] && x <= Tree[Tree[Now].Son[0]].Size)
Now = Tree[Now].Son[0];
else if (x > Tree[Tree[Now].Son[0]].Size + Tree[Now].Cnt)
{
x -= (Tree[Tree[Now].Son[0]].Size + Tree[Now].Cnt);
Now = Tree[Now].Son[1];
}
else return Now;
}
}
LL GetPre(LL x)
{
Find( x );
if (Tree[Root].Val < x)
return Root;
LL Now = Tree[Root].Son[0];
while (Tree[Now].Son[1])
Now = Tree[Now].Son[1];
return Now;
}
LL GetBack(LL x)
{
Find( x );
if (Tree[Root].Val > x)
return Root;
LL Now = Tree[Root].Son[1];
while (Tree[Now].Son[0])
Now = Tree[Now].Son[0];
return Now;
}
void Delete(LL x)
{
LL Over = GetPre( x ), Next = GetBack( x );
Splay(Over, 0);
Splay(Next, Over);
LL Del = Tree[Next].Son[0];
if (Tree[Del].Cnt > 1)
{
Tree[Del].Cnt --;
Splay(Del, 0);
}
else Tree[Next].Son[0] = 0;
}
}_;
int main()
{
LL n;
read( n );
_.Insert( INF );
_.Insert( - INF );
for (Int i = 1; i <= n; ++ i)
{
LL Or, x;
read( Or ); read( x );
switch ( Or )
{
case 1:
{
_.Insert( x );
break;
}
case 2:
{
_.Delete( x );
break;
}
case 3:
{
_.Find( x );
printf("%lld\n", _.Tree[_.Tree[_.Root].Son[0]].Size);
break;
}
case 4:
{
printf("%lld\n", _.Tree[_.GetKth(x + 1)].Val);
break;
}
case 5:
{
printf("%lld\n", _.Tree[_.GetPre( x )].Val);
break;
}
case 6:
{
printf("%lld\n", _.Tree[_.GetBack( x )].Val);
break;
}
}
}
return 0;
}
咕咕咕
#include
#include
#include
#include
#include
#define MAXN 100005
#define LL int
//#define LL long long
#define Int register int
using namespace std;
inline void read(LL &x)
{
x = 0;
LL f = 1;
char s = getchar();
while (s < '0' || s > '9')
{
if (s == '-')
f = -1;
s = getchar();
}
while (s >= '0' && s <= '9')
{
x = (x << 3) + (x << 1) + (s ^ 48);
s = getchar();
}
x *= f;
}
inline LL Max(LL x,LL y)
{
return x > y ? x : y;
}
inline LL Min(LL x,LL y)
{
return x < y ? x : y;
}
inline void Swap(LL &x,LL &y)
{
LL temp = x;
x = y;
y = temp;
}
LL n, a[MAXN];
struct Splayer
{
LL Root, tot;
struct SplayTree
{
bool Lazy_Reverse;
LL Fa, Son[2], Lazy_Add, Size, Maxx, Minn, Val;
}Tree[MAXN << 1];
inline void NewNode(LL Zhi)
{
tot ++;
Tree[tot].Size = 1;
Tree[tot].Val = Tree[tot].Maxx = Tree[tot].Minn = Zhi;
//printf("%lld %lld %lld\n", tot, Tree[tot].Val, Tree[tot].Minn);
Tree[tot].Son[0] = Tree[tot].Son[1] = Tree[tot].Fa = Tree[tot].Lazy_Add = Tree[tot].Lazy_Reverse = 0;
}
inline void Update(LL x)
{
Tree[x].Size = Tree[Tree[x].Son[0]].Size + Tree[Tree[x].Son[1]].Size + 1;
Tree[x].Maxx = Tree[x].Minn = Tree[x].Val;
if (Tree[x].Son[0])
Tree[x].Maxx = Max(Tree[x].Maxx, Tree[Tree[x].Son[0]].Maxx),
Tree[x].Minn = Min(Tree[x].Minn, Tree[Tree[x].Son[0]].Minn);
if (Tree[x].Son[1])
Tree[x].Maxx = Max(Tree[x].Maxx, Tree[Tree[x].Son[1]].Maxx),
Tree[x].Minn = Min(Tree[x].Minn, Tree[Tree[x].Son[1]].Minn);
}
void PushDown(LL x)
{
if (Tree[x].Lazy_Add)
{
if (Tree[x].Son[0])
{
Tree[Tree[x].Son[0]].Val += Tree[x].Lazy_Add;
Tree[Tree[x].Son[0]].Lazy_Add += Tree[x].Lazy_Add;
Tree[Tree[x].Son[0]].Maxx += Tree[x].Lazy_Add;
Tree[Tree[x].Son[0]].Minn += Tree[x].Lazy_Add;
}
if (Tree[x].Son[1])
{
Tree[Tree[x].Son[1]].Val += Tree[x].Lazy_Add;
Tree[Tree[x].Son[1]].Lazy_Add += Tree[x].Lazy_Add;
Tree[Tree[x].Son[1]].Maxx += Tree[x].Lazy_Add;
Tree[Tree[x].Son[1]].Minn += Tree[x].Lazy_Add;
}
Tree[x].Lazy_Add = 0;
}
if (Tree[x].Lazy_Reverse)
{
if (Tree[x].Son[0])
Tree[Tree[x].Son[0]].Lazy_Reverse ^= 1;
if (Tree[x].Son[1])
Tree[Tree[x].Son[1]].Lazy_Reverse ^= 1;
Swap(Tree[x].Son[0], Tree[x].Son[1]);
Tree[x].Lazy_Reverse = 0;
}
}
void Build(LL x,LL Pre)
{
if (x == n + 2)
return ;
NewNode( a[x] );
if ( Pre )
{
Tree[tot].Fa = Pre;
Tree[Pre].Son[1] = tot;
}
else Root = tot;
LL Now = tot;
Build(x + 1, tot);
Update( Now );
}
bool ZY(LL x)
{
return (Tree[Tree[x].Fa].Son[1] == x);
}
inline void Rotate(LL x)
{
LL y = Tree[x].Fa;
LL z = Tree[y].Fa;
bool Fx = ZY( x );
LL B = Tree[x].Son[Fx ^ 1];
Tree[y].Son[Fx] = B;
Tree[B].Fa = y;
Tree[z].Son[ZY( y )] = x;
Tree[x].Fa = z;
Tree[x].Son[Fx ^ 1] = y;
Tree[y].Fa = x;
Update( y );
Update( x );
}
inline void Splay(LL x,LL Goal)
{
while (Tree[x].Fa != Goal)
{
LL y = Tree[x].Fa;
LL z = Tree[y].Fa;
if (z != Goal)
{
if (ZY( x ) == ZY( y ))
Rotate( y );
else Rotate( x );
}
Rotate( x );
}
if (! Goal)
Root = x;
}
inline void ToPlace(LL Kth,LL Pos)
{
LL Now = Root;
while (1 + 1 == 2)
{
PushDown( Now );
LL Len = Tree[Tree[Now].Son[0]].Size + 1;
if (Kth == Len)
break ;
if (Kth < Len)
Now = Tree[Now].Son[0];
if (Kth > Len)
Kth -= Len, Now = Tree[Now].Son[1];
}
Splay(Now, Pos);
}
inline void Add_QJ(LL l,LL r,LL Zhi)
{
ToPlace(l, 0);
ToPlace(r + 2, Root);
LL Cha = Tree[Tree[Root].Son[1]].Son[0];
Tree[Cha].Lazy_Add += Zhi;
Tree[Cha].Maxx += Zhi;
Tree[Cha].Minn += Zhi;
Tree[Cha].Val += Zhi;
}
inline void Insert(LL Pos,LL Zhi)
{
ToPlace(Pos + 1, 0);
ToPlace(Pos + 2, Root);
NewNode( Zhi );
Tree[Tree[Root].Son[1]].Son[0] = tot;
Tree[tot].Fa = Tree[Root].Son[1];
}
inline void Delete(LL Pos)
{
ToPlace(Pos, 0);
ToPlace(Pos + 2, Root);
Tree[Tree[Root].Son[1]].Son[0] = 0;
}
inline LL GetMax(LL l,LL r)
{
ToPlace(l, 0);
ToPlace(r + 2, Root);
return Tree[Tree[Tree[Root].Son[1]].Son[0]].Maxx;
}
inline LL GetMin(LL l,LL r)
{
ToPlace(l, 0);
ToPlace(r + 2, Root);
return Tree[Tree[Tree[Root].Son[1]].Son[0]].Minn;
}
inline void Reverse(LL l,LL r)
{
if (l == r)
return ;
ToPlace(l, 0);
ToPlace(r + 2, Root);
Tree[Tree[Tree[Root].Son[1]].Son[0]].Lazy_Reverse ^= 1;
}
inline void Revolve(LL l,LL r,LL Wei)
{
LL Len = r - l + 1;
LL Fen = (Wei % Len + Len) % Len;
if ( Fen )
{
ToPlace(r - Fen + 1, 0);
ToPlace(r + 2, Root);
LL Zhuan = Tree[Tree[Root].Son[1]].Son[0];
Tree[Tree[Root].Son[1]].Son[0] = 0;
ToPlace(l, 0);
ToPlace(l + 1, Root);
Tree[Tree[Root].Son[1]].Son[0] = Zhuan;
Tree[Zhuan].Fa = Tree[Root].Son[1];
}
}
}Fuck_;
int main()
{
read( n );
for (Int i = 1; i <= n; ++ i)
read( a[i] );
Fuck_.Build(0, 0);
LL m;
read( m );
for (Int i = 1; i <= m; ++ i)
{
char Or[8];
scanf("%s", Or);
switch ( Or[0] )
{
case 'A':
{
LL l, r, x;
read( l ); read( r ); read( x );
Fuck_.Add_QJ(l, r, x);
break;
}
case 'R':
{
switch ( Or[3] )
{
case 'E':
{
LL l, r;
read( l ); read( r );
Fuck_.Reverse(l, r);
break;
}
case 'O':
{
LL l, r, x;
read( l ); read( r ); read( x );
Fuck_.Revolve(l, r, x);
break;
}
}
break;
}
case 'I':
{
LL x, Zhi;
read( x ); read( Zhi );
Fuck_.Insert(x, Zhi);
break;
}
case 'D':
{
LL x;
read( x );
Fuck_.Delete( x );
break;
}
case 'M':
{
LL l, r;
read( l ); read( r );
LL Ans = Fuck_.GetMin(l, r);
printf("%d\n", Ans);
break;
}
}
}
return 0;
}
咕咕咕
咕咕咕
咕咕咕
咕咕咕