写了一发splay的模板,在正常的应用中应该是够用了。除了具备splay特有的功能比如区间的分裂与合并,还具有所有treap的功能,同时也可以维护区间的最大值最小值。对于初次学习splay比较有帮助。
有毒!
#include<bits/stdc++.h>
using namespace std;
const int INF=2147483647;
int n;
struct Node
{
int key,siz; //键值和大小
Node* ch[2]; //左右儿子指针
int add; //懒标记
int sum,minv,maxv; //维护的和、最大值、最小值
bool flip; //翻转标记
Node(int key);
int cmp(int);
void maintain();
void push_down();
}*null,*root,*limt;
//初始化结点信息
Node :: Node(int key=0):key(key)
{
sum=key;
maxv=-INF;
minv=INF;
siz=(null?1:0);
ch[0]=ch[1]=null;
add=flip=0;
}
//返回当前结点向下查找第k个元素的方向
int Node :: cmp(int k)
{
if(k==ch[0]->siz+1) return -1;
else return k<ch[0]->siz+1?0:1;
}
//上传更新数据
void Node :: maintain()
{
siz=1+ch[1]->siz+ch[0]->siz;
minv=maxv=sum=key;
if(ch[0]!=limt)
sum+=ch[0]->sum;
minv=min(minv,ch[0]->minv),
maxv=max(maxv,ch[0]->maxv);
if(ch[1]!=limt)
sum+=ch[1]->sum;
minv=min(minv,ch[1]->minv),
maxv=max(maxv,ch[1]->maxv);
return;
}
//下传标记,先翻转后加add 与 先加add后翻转的效果相同
void Node :: push_down()
{
if(flip)
{
flip=false;
swap(ch[0],ch[1]);
ch[0]->flip^=1;
ch[1]->flip^=1;
}
if(add)
{
if(ch[0]!=limt) ch[0]->add+=add;
if(ch[1]!=limt) ch[1]->add+=add;
key+=add;
add=0;
}
}
//按顺序输出序列
void Print(Node* cur)
{
if(cur==null) return;
cur->push_down();
Print(cur->ch[0]);
if(cur->key) cout<<cur->key<<" ";
Print(cur->ch[1]);
}
//旋转,dir==0为左旋,反之为右旋
void Rotate(Node* &cur,int dir)
{
Node* tmp=cur->ch[dir^1];
cur->ch[dir^1]=tmp->ch[dir];
tmp->ch[dir]=cur;
cur->maintain();
tmp->maintain();
cur=tmp;
}
//将第k个元素伸展到cur结点上
void Splay(Node* &cur,int k)
{
cur->push_down();
int dir=cur->cmp(k);
if(dir==1) k-=cur->ch[0]->siz+1;
if(~dir)
{
Node* tmp=cur->ch[dir];
tmp->push_down();
int dir2=tmp->cmp(k);
int k2=dir2==1?k-tmp->ch[0]->siz-1:k;
if(~dir2)
{
Splay(tmp->ch[dir2],k2);
if(dir==dir2) Rotate(cur,dir^1);
else Rotate(cur->ch[dir],dir);
}
Rotate(cur,dir^1);
}
return;
}
//合并两个区间到left中
void Merge(Node* &left,Node* right)
{
left->push_down();
Splay(left,left->siz);
left->ch[1]=right;
left->maintain();
return;
}
//把区间分为大小为k的部分和剩余的部分
void Split(Node* o,int k,Node* &left,Node* &right)
{
o->push_down();
Splay(o,k);
left=o;
right=o->ch[1];
o->ch[1]=null;
left->maintain();
return;
}
//向序列尾添加键值为_key的元素
void Push_back(int _key)
{
Node* add=new Node(_key);
Merge(root,add);
}
//在pos位置上(第pos个元素后)添加键值为_key的元素
void Insert(int _key,int pos)
{
Node* add=new Node(_key);
pos++;
Node *left,*right;
Split(root,pos,left,right);
Merge(left,add);
Merge(left,right);
root=left;
}
//删除在位置pos的元素
void Delete(int pos)
{
pos++;
Node *left,*right,*mid,*g;
Split(root,pos,g,right);
Split(g,pos-1,left,mid);
Merge(left,right);
root=left;
}
//把从beg开始的长度为len的区间移动到to位置
void Move_seg(int beg,int len,int to)
{
beg++,to++;
Node *left,*mid,*right,*g,*rot=root;
Split(root,beg-1,left,g);
Split(g,len,mid,right);
Merge(left,right);
Node *l,*r;
Split(left,to-len,l,r);
Merge(l,mid);
Merge(l,r);
root=l;
}
//将区间a到b加上val,如果flip_mark为真,则同时翻转
void Change(int a,int b,int val,bool flip_mark)
{
a++,b++;
Node *left,*right,*g,*mid;
Split(root,a-1,left,g);
Split(g,b-a+1,mid,right);
mid->add+=val;
mid->flip^=flip_mark;
Merge(left,mid);
Merge(left,right);
root=left;
}
//查询维护的值
#define mp make_pair
typedef pair<int,pair<int,int> > ppi;
ppi Query(int a,int b)
{
a++,b++;
Node *left,*right,*g,*mid;
Split(root,a-1,left,g);
Split(g,b-a+1,mid,right);
return mp(mid->sum,mp(mid->maxv,mid->minv));
}
int main()
{
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
null=new Node();
root=new Node(); //虚拟结点防止崩溃
limt=new Node();
scanf("%d",&n);
for(int i=1;i<=n;i++) Push_back(i);
Print(root); cout<<endl;
Insert(76,2);
cout<<"Insert(76,2);\n";
Print(root); cout<<endl;
Delete(7);
cout<<"Delete(7);\n";
Print(root); cout<<endl;
Move_seg(3,3,8);
cout<<"Move_seg(3,3,8);\n";
Print(root); cout<<endl;
Change(1,3,1,0);
cout<<"Change(1,3,1,0);\n";
Print(root); cout<<endl;
Change(1,3,0,1);
cout<<"Change(1,3,0,1);\n";
Print(root); cout<<endl;
ppi ans=Query(2,5);
cout<<"Query(2,5);\n";
cout<<"sum:"<<ans.first<<" max:"<<ans.second.first<<" min:"<<ans.second.second<<endl;
return 0;
}
/***
因为有虚拟结点的存在,占用了第一个位置,而且实际上该结点是不存在的,所以每次有涉及结点位置的信息时都要将对应坐标++来与存储的序列对应
***/