【模板Splay】XX树

话说 大概半年前专门去学了下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;
}

“`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值