简介
Splay的伸展操作使Splay在某些方面变得很方便,甚至也能够利用伸展操作来进行区间操作!
ps:本人蒟蒻,写法可能很奇怪:P。
再ps:首先结构体内部的cmp要写的特殊一点(因为k表示位置):
int cmp(int &k)
{
if (k==son[0]->si+1) return -1;if (k<son[0]->si+1) return 0;
k-=son[0]->si+1;return 1;
}
如果k往右边走,那么和寻找第k大一样,要减去son[0]->si+1。
序列转Splay
对于一个序列,我们可以把他变成一棵Splay,并使Splay的中序遍历和该序列相同。这样的话,序列中的一段就可以和Splay相对应了。实现如下:
递归调用即可。
P_node Build(int L,int R)
{
if (L>R) return null;
int mid=L+(R-L>>1);
P_node now=newNode(mid,a[mid]);
now->son[0]=Build(L,mid-1);now->son[1]=Build(mid+1,R);Pushup(now);
return now;
}
定位
有了伸展操作之后,只要把L-1伸展到根,然后把R+1伸展到根的右儿子,L~R这一块区间就被定位在根的右儿子的左儿子了(很好yy,不解释了)。
void Change(P_node &ro,int L,int R)
{
L--;R++;
Splay(ro,L);ro->cmp(R);
Splay(ro->son[1],R);
}
抽取
(本蒟蒻的操作中有个)抽取操作,就是把L~R从当前Splay中脱离。有了定位操作之后很容易写,不要忘记更新信息。
P_node Split(P_node &ro,int L,int R)
{
Change(ro,L,R);
P_node now=ro->son[1]->son[0];
ro->son[1]->son[0]=null;
ro->son[1]->Pushup();ro->Pushup();
return now;
}
插入
(本蒟蒻的操作中有个)插入操作,就是把一棵新的Splay插入到当前Splay的L后。
void Insert(P_node &ro,int L,P_node now)
{
Change(ro,L+1,L);
ro->son[1]->son[0]=now;
ro->son[1]->Pushup();ro->Pushup();
}
翻转
记录flip翻转标记,每次Pushdown,就和线段树一样。
总值
同理,用Lazy-tag即可。
模板
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=100000;
int n,te,a[maxn+5];
//=========================================================================
struct Node
{
Node *son[2];
int x,num,si;
LL tag,sum;
bool flip;
int cmp(int &k)
{
if (k==son[0]->si+1) return -1;if (k<son[0]->si+1) return 0;
k-=son[0]->si+1;return 1;
}
};
typedef Node* P_node;
Node tem[maxn+5];
P_node null=tem,ro=null,P_tem=null;
P_node newNode(int k,int num)
{
P_tem++;P_tem->x=k;P_tem->si=1;
P_tem->num=num;P_tem->tag=P_tem->sum=P_tem->flip=0;
P_tem->son[0]=P_tem->son[1]=null;
return P_tem;
}
void Pushup(P_node &id)
{
id->si=id->son[0]->si+id->son[1]->si+1; //更新节点个数
id->sum=id->son[0]->sum+id->son[1]->sum+id->num; //更新总和
}
void Pushdown(P_node &id)
{
if (id->flip) //翻转标记
{
swap(id->son[0],id->son[1]);id->flip=false;
if (id->son[0]!=null) id->son[0]->flip^=1;
if (id->son[1]!=null) id->son[1]->flip^=1;
}
LL tag=id->tag;id->tag=0; //加和标记
if (id->son[0]!=null)
{
id->son[0]->sum+=tag*id->son[0]->si;
id->son[0]->tag+=tag;id->son[0]->num+=tag;
}
if (id->son[1]!=null)
{
id->son[1]->sum+=tag*id->son[1]->si;
id->son[1]->tag+=tag;id->son[1]->num+=tag;
}
}
void LNR(P_node &id) //遍历
{
if (id==null) return;Pushdown(id);
LNR(id->son[0]);printf("%d\n",id->x);LNR(id->son[1]);
}
void Rotate(P_node &id,int d)
{
P_node t=id->son[d^1];id->son[d^1]=t->son[d];t->son[d]=id;
Pushup(id);Pushup(t);id=t;
}
P_node Build(int L,int R) //将L~R的a序列变成Splay
{
if (L>R) return null;
int mid=L+(R-L>>1);
P_node now=newNode(mid,a[mid]);
now->son[0]=Build(L,mid-1);now->son[1]=Build(mid+1,R); //递归调用
Pushup(now); //节点更新
return now;
}
void Splay(P_node &id,int k)
{
Pushdown(id); //传递Lazy-tag
int d1=id->cmp(k);
if (d1!=-1)
{
P_node p=id->son[d1];Pushdown(p); //传递Lazy-tag
int d2=p->cmp(k);
if (d2!=-1)
{
Splay(p->son[d2],k);
if (d1==d2) Rotate(id,d1^1),Rotate(id,d1^1); else
Rotate(id->son[d1],d2^1),Rotate(id,d1^1);
} else Rotate(id,d1^1);
}
}
void Change(P_node &ro,int L,int R)
{
//将L-1旋到根,R+1旋到根的右儿子,则L~R就是根的右儿子的左儿子
L--;R++;
Splay(ro,L);ro->cmp(R);
Splay(ro->son[1],R);
}
void Insert(P_node &ro,int L,P_node now) //在L后插入now
{
Change(ro,L+1,L); //将L旋到根,L+1旋到根的右儿子,根的右儿子的左儿子必定为空
ro->son[1]->son[0]=now; //加入now
Pushup(ro->son[1]);Pushup(ro); //更新节点
}
P_node Split(P_node &ro,int L,int R)
{
Change(ro,L,R);
P_node now=ro->son[1]->son[0]; //抽出L~R
ro->son[1]->son[0]=null; //子树没了
Pushup(ro->son[1]);Pushup(ro); //更新节点
return now;
}
//=========================================================================
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x) //读入优化
{
int tot=0,f=1;char ch=getchar(),c=ch;
while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;c=ch;ch=getchar();}
if (c=='-') f=-f;
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
x=tot*f;
return Eoln(ch);
}
void make_Splay(int L,int R) {ro=Build(L,R);} //初始化Splay
int main()
{
freopen("Splay.in","r",stdin);
freopen("Splay.out","w",stdout);
readi(n);make_Splay(0,n+1);
//必须在1前面和n后面加两个节点,否则调用L-1和R+1要炸掉,但是由于加了节点,所以调用函数要注意
return 0;
}