话说 大概半年前专门去学了下Splay,在bzoj上写了几道题
但是,当时觉得自己很菜,想多学点东西,再一次性写出来,然后。。。。
我现在都忘掉了自己还会这个 mmp。
要讲Splay,那就必然需要了解一下旋转操作。
http://www.cnblogs.com/kuangbin/archive/2012/10/07/2714068.html
先去研究一下上述博客知识,学一下旋转操作的原理! 很重要
然后我找到了黄学长的Splay 模板,全数组构成,哇,真是好开心啊,收藏收藏:
http://hzwer.com/2841.html
以下的代码 基本都是按照kuangbin 的代码风格来写的
我的理解:
我们构造一颗二叉排序树,然后我们所有的一切操作:插入、删除、反转、旋转 都是建立在 维护这棵二叉排序树上的。但是我们要搞清楚的一点是:这棵排序树并不是以大小来排序的,而是以 我们要处理的数组 的下标来排序。 所以我们一直都在维护这个数组的相对顺序,而不是大小。
旋转 Rotate :有三种情况需要讨论 : (PS:kuangbin 博客的 单旋图貌似是有问题的,很明显的相对顺序都没有维护)
1. 节点x的父节点y是根节点。
![]()
2 Zig-Zig或Zag-Zag操作
节点x的父节点y不是根节点,且x与y同时是各自父节点的左孩子或者同时是各自父节点的右孩子。
![]()
3.Zig-Zag或Zag-Zig操作:节点x的父节点y不是根节点,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子。
![]()
我们可以看到这三种情况的旋转我们都将X旋转至根节点。 并且我们同时维护了他们的相对顺序大小。
我们如何实现上述的操作?:
通过入门题来讲解讲解:
POJ 3468 成段更新+区间求和
* 题目给定了n个数A1,A2,…An,有以下两种操作
* C a b c:把c加入到Aa,Aa+1,..Ab中
* Q a b:查询Aa,Aa+1,..Ab的和
开的空间:
#define key_value ch[ch[root][1]][0] //经常利用这个
#define N 200005
const int INF=0x3f3f3f3f;
int pre[N],ch[N][2],key[N],size[N],add[N],rev[N],m[N];
/*pre:父节点 ch[N][0][1]左节点和右节点。 size子树规模,root树的当前的根 , key:这个节点代表的值 add增量延迟标记 m记录子树和。 */
int root,tot;
int n;
int a[N];
void newnode(int &r,int father,int k){ //注意r 需要引用
r=++tot; //可知我们的根节点从1开始
pre[r]=father; //更新 pre
size[r]=1; //新开节点只有自己
add[r]=rev[r]=0; // 标记初识为0
ch[r][0]=ch[r][1]=0;
key[r]=m[r]=k;
}
void push_rev(int r){ //反转操作
if(r){
swap(ch[r][0],ch[r][1]); //把当前r的左右子树反转,并且把反转传递下去。
rev[r]^=1;
}
}
void up_add(int r,int d){ // 给根为r的子树增加值,一定要先把当前节点的标记更新掉,然后再加上延迟标记!
if(r==0) return;
add[r]+=d;
key[r]+=d;
m[r]+=d;
}
void push_down(int r){ //将lazy 标记更新到孩子节点
if(rev[r]){
push_rev(ch[r][0]);
push_rev(ch[r][1]);
rev[r]=0;
}
if(add[r]){
up_add(ch[r][0],add[r]);
up_add(ch[r][1],add[r]);
add[r]=0;
}
}
void push_up(int r){ //向上更新
size[r]=size[ch[r][0]] + size[ch[r][1]] +1;
m[r]=key[r];
if(ch[r][0])
m[r]=min(m[ch[r][0]] , m[r]); // 因为我们的删除就是归零,所以要判断是否存在
if(ch[r][1])
m[r]=min(m[ch[r][1]] , m[r]);
}
void build(int &x,int l,int r,int father){ ////先建立中间结点,再两端的方法
if(l>r) return;
int mid=(l+r)>>1;
newnode(x,father,a[mid]); //key=a[mid], 我们按照数组的下标来建树
build(ch[x][0],l,mid-1,x); // x =father
build(ch[x][1],mid+1,r,x);
push_up(x);
}
void init(){ //kuangbin一惯的风格init()
tot=root=0;
ch[root][0]=ch[root][1]=pre[root]=size[root]=rev[root]=0;
//root 地址为0,所以不需要赋m[root]的值
/*开一个理论上永远最大的根,和永远最小的根,这样操作有套路*/
newnode(root,0,INF); // 这个点是永远最小的
newnode(ch[root][1],root,INF); //这个点永远最大。
build(key_value,1,n,ch[root][1]); //建树
push_up(ch[root][1]); //记得建完树之后更新到root
push_up(root);
}
void Rotate(int x,int kind)//对X旋转,0为左旋,1为右旋 该部分基本固定
{
int y=pre[x];
push_down(y); //正确的顺序先传递y,然后再传递x
push_down(x);
ch[y][!kind]=ch[x][kind];
pre[ch[x][kind]]=y;
if(pre[y])
ch[pre[y]][ch[pre[y]][1]==y]=x;
pre[x]=pre[y];
ch[x][kind]=y;
pre[y]=x;
push_up(y);
}
void Splay(int r,int goal) ////Splay调整,将结点r调整到goal下面
{
push_down(r);
while(pre[r]!=goal)
{
if(pre[pre[r]]==goal)
{
//这题有反转操作,需要先push_down,在判断左右孩子
push_down(pre[r]);
push_down(r);
Rotate(r,ch[pre[r]][0]==r);
}
else
{
//这题有反转操作,需要先push_down,在判断左右孩子
push_down(pre[pre[r]]);
push_down(pre[r]);
push_down(r);
int y=pre[r];
int kind=(ch[pre[y]][0]==y);
//两个方向不同,则先左旋再右旋
if(ch[y][kind]==r)
{
Rotate(r,!kind);
Rotate(r,kind);
}
//两个方向相同,相同方向连续两次
else
{
Rotate(y,kind);
Rotate(r,kind);
}
}
}
push_up(r);
if(goal==0)root=r;
}
int Get_Kth(int r,int k) //我们得到的是第k个节点的位置。但是我们注意init的时候添加两个极大极小点。
{
Push_Down(r);
int t=size[ch[r][0]]+1;// 我们添加过一个很小的点。
if(t==k)return r;
if(t>k)return Get_Kth(ch[r][0],k);
else return Get_Kth(ch[r][1],k-t);
}
int Get_Min(int r) //找到最值
{
Push_Down(r);
while(ch[r][0])
{
r=ch[r][0];
Push_Down(r);
}
return r;
}
int Get_Max(int r)
{
Push_Down(r);
while(ch[r][1])
{
r=ch[r][1];
Push_Down(r);
}
return r;
}
void ADD(int l,int r,int D)
{
Splay(get_kth(root,l),0); //l-1
Splay(get_kth(root,r+2),root); //r+1
up_add(key_value,D);
push_up(ch[root][1]);
push_up(root);
}
void REVERSE(int l,int r){
Splay(get_kth(root,l),0); //将第l-1个节点调制0
Splay(get_kth(root,r+2),root); //将r+1旋转至新的root,那么key_value就是(l-1,r+2)
push_rev(key_value);
push_up(ch[root][1]);
push_up(root);
}
void REVOLVE(int l,int r,int T) //循环右移
{
int len=r-l+1; // len:长度
T=(T%len+len)%len; //
if(T==0)return;
int c=r-T+1;//将区间[c,r]放在[l,c-1]前面
Splay(get_kth(root,c),0); //将root
Splay(get_kth(root,r+2),root);
int tmp=key_value;
key_value=0;
push_up(ch[root][1]);
push_up(root);
Splay(get_kth(root,l),0);
Splay(get_kth(root,l+1),root);
key_value=tmp;
pre[key_value]=ch[root][1];//这个不用忘记
push_up(ch[root][1]);
push_up(root);
}
void insert(int x,int p){
Splay(get_kth(root,x+1),0); // x转至根
Splay(get_kth(root,x+2),root); //x+1转到root右儿子,此时x+2.... 都在右边
newnode(key_value,ch[root][1],p);
push_up(ch[root][1]);
push_up(root);
}
void del(int x){
Splay(get_kth(root,x),0); //x-1 移到0
Splay(get_kth(root,x+2),root); //x+1 移到x-1的右边 那么key_value=x
pre[key_value]=0;
key_value=0; //不知道算不算清除 //
push_up(ch[root][1]);
push_up(root);
}
int qur_min(int l,int r){
Splay(get_kth(root,l),0);
Splay(get_kth(root,r+2),root);
return m[key_value];
}
int main() {
// freopen("1.txt","r",stdin);
while (~scanf("%d",&n)) {
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
init();//这个不能忘记
int q;
scanf("%d",&q);
char op[10];
while(q--){
scanf("%s",op);
if(op[0]=='A'){
int l,r,ad;
scanf("%d %d %d",&l,&r,&ad);
ADD(l,r,ad);
}
else if(op[0]=='I'){
int x,p;
scanf("%d %d",&x,&p);
insert(x,p);
}
else if(op[0]=='D'){
int x;
scanf("%d",&x);
del(x);
}
else if(op[0]=='M'){
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",qur_min(l,r));
}
else if(op[3]=='E'){ //REVERSE 反转
int l,r;
scanf("%d %d",&l,&r);
REVERSE(l,r);
}
else{ // REVOLVE
int l,r,k;
scanf("%d %d %d",&l,&r,&k);
REVOLVE(l,r,k);
}
}
}
return 0;
}
“`