bzoj1208 splay伸展树

splay伸展树主要有两种操作形式

(1)正常的二叉树插入形式

功能:a、查找 b、求最大值 c、最小值 d、求前驱 e、求后继 f、删点 g、合并splay树

(这里的删除直接利用splay树的结点下标)

(2)区间形式 (插入是以区间形式插入的)

区间形式的伸展树相当于线段树,支持线段树的所有操作,并且还支持区间插入这个功能,

比如操作区间[a,b],将根设为a-1,根的右孩子设为b+1,那么根的右孩子的左孩子就是所求区间

某个点插入区间也是一个道理

需要注意的是,这里init()自动生成了左右区间,方便之后的操作。注意实际有效的区间范围即可。

(区间删除或者说其他操作,利用的是原序列的下标,再通过函数找到对应的splay结点下标,转换到区间,再对区间删除)

这道题主要是二叉树的插入形式

需要用到 插入 找前驱 找后继 删点 合并 这几个功能

删点就是将所需要删除的点splay到根,去掉联系,合并左右子树即可

合并过程是将左子树的最大键值结点提为根,右子树插入左子树的根即可。

当左子数没有的时候,合并的结果直接是右子树。。

标记一下当前树是动物或者人即可用一棵splay树来完成

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <math.h>
#include <vector>
#include <cstdio>
#include <string>
#include<string.h>
#include <fstream>
#include <iostream>
#include <algorithm>
using namespace std;
#define exp 1e-8
#define INF 0x3f3f3f3f
#define ll long long
#define set(a,b) memset(a,b,sizeof(a));
#define set(a,b) memset(a,b,sizeof(a));
#define for1(a,b) for(int a=1;a<=b;a++)//1---(b)
#define for0(a,b) for(int a=0;a<=b;a++)//0---(b)
void bug(string st="bug")
{cout<<st<<endl;}
template<typename __ll>
inline void READ(__ll &m){
    __ll x=0,f=1;char ch=getchar();
    while(!(ch>=‘0‘&&ch<=‘9‘)){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();}
    m=x*f;
}
template<typename __ll>
inline void read(__ll &m){READ(m);}
template<typename __ll>
inline void read(__ll &m,__ll &a){READ(m);READ(a);}
template<typename __ll>
inline void read(__ll &m,__ll &a,__ll &b){READ(m);READ(a);READ(b);}
const int MAXN=100000+1000;
#define dat int
#define Key_value ch[ch[root][1]][0]
struct splaytree
{
    int ch[MAXN][2];  //孩子
    int pre[MAXN];   //父亲
    dat key[MAXN];  //键值
    int size[MAXN];  //大小
    int root,tot1;  //根  结点大小
    int s[MAXN],tot2;  //删除用的数组,记录删除了那些点,重复利用删除的点
    int kind,ans;
    void addnode(int &r,int father,dat k)
    {
        if(tot2) r=s[tot2--];
        else r=++tot1;
        ch[r][0]=ch[r][1]=0;
        pre[r]=father;
        key[r]=k;
        size[r]=1;
    }
    void push_up(int r)
    {
        if(!r)return ;  //添加这句,以防对0结点不小心操作
       int lson=ch[r][0],rson=ch[r][1];
       size[r]=1+size[lson]+size[rson];
    }
    void init()
    {
        kind=-1,ans=0;
        root=tot1=tot2=0;
        ch[0][0]=ch[0][1]=pre[0]=size[0]=0;
        key[0]=-INF;
    }
    void rotate(int x,int kind)
    {
        int y=pre[x];
        int z=pre[y];
        ch[y][!kind]=ch[x][kind];
        pre[ch[x][kind]]=y;
        if(z)   ch[z][ch[z][1]==y] = x;
        pre[x]=pre[y];
        ch[x][kind]=y;
        pre[y]=x;
        push_up(y);
    }
    void splay(int r,int goal)  //r是splay的结点下标
    {
        while(pre[r]!=goal)    //各类push_down已经在rotate中执行
        {
            if(pre[pre[r]]==goal)
            {
                rotate(r,ch[pre[r]][0]==r);
                continue;
            }
            int y=pre[r];
            int z=pre[y];
            int kind=  ch[z][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 insert(int &x,int f,int val)  //返回插入结点的下标
    {
        if(x==0)
        {
            addnode(x,f,val);
            int tmp=x;
            splay(tmp,0);
            return tmp;  //如果是这样子插入的话,
        }                //由于是int &x,splay会将ch[pre[x]]][]修改,导致x也改变了
        if(val==key[x])
            return x;
        if(val<key[x])
             return insert(ch[x][0],x,val);
        else
            return insert(ch[x][1],x,val);
    }
    void erase(int r)    //重复利用删除的结点
    {
        if(!r)return ;
        s[++tot2]=r;
        pre[r]=0;  //如此就截断了关系了
    }
    int get_next()    //返回后缀的结点下标
    {
        int r=root;
        r=ch[r][1];  //开始使劲靠左找后继
        while(ch[r][0])
            r=ch[r][0];
        return r;
    }
    int get_pre()  //返回前缀的结点下标
    {
        int r=root;
        r=ch[r][0];
        while(ch[r][1])
            r=ch[r][1];
        return r;
    }
    int get_max(int r)   //获得当前树最大键值的结点下标
    {
        while(ch[r][1])
            r=ch[r][1];
        return r;
    }
    void Union(int lroot,int rroot)  //合并两课树
    {
        if(lroot==0)  //左子树没有,root直接等于右子树
        {
            root=rroot;
            return ;
        }
        int r=get_max(lroot); //获得最大的左子树的最大的某一个点
        splay(r,0);   //并将其设为根
        ch[root][1]=rroot;  //合并
        pre[rroot]=root;
        push_up(root);
    }
    void cut_root(int idx)  //砍掉某一个结点,
    {
        splay(idx,0);
        int r=root;
        int lroot=ch[r][0];
        int rroot=ch[r][1];
        pre[lroot]=pre[rroot]=0;
        erase(r);
        Union(lroot,rroot);
    }
    int find(int val,int r)  //查找,如果找到返回结点下标
    {
        if(!r) return 0;
        if(val==key[r])
            return r;
        if(val<key[r])
            return find(val,ch[r][0]);
        return find(val,ch[r][1]);
    }
    void solve(int val)
    {
        int r=find(val,root);
        if(!r)  //没找到同一个值的宠物 人
        {
            r=insert(root,0,val);
            splay(r,0);
            int preidx=get_pre();
            int nextidx=get_next();
            if((preidx!=0&&nextidx==0)||(preidx!=0&&nextidx!=0&&abs(key[preidx]-key[r])<=abs(key[nextidx]-key[r])))
            {
                ans+=abs(key[preidx]-key[r]);
                cut_root(preidx);
            }
            else
            {
                ans+=abs(key[nextidx]-key[r]);
                cut_root(nextidx);
            }
            push_up(root);
        }
        ans%=1000000;
        cut_root(r);
        push_up(root);
        if(size[root]==0)
            kind=-1;
    }
}t;
int main()
{

    int n,a,b,ans;
    read(n);
    t.init();
    while(n--)
    {
        read(a,b);
        if(t.kind==-1||t.kind==a)
            t.insert(t.root,0,b),t.kind=a;
        else
            t.solve(b);
    }
    printf("%d\n",t.ans);
    return 0;
}

时间: 2024-10-29 04:01:43

bzoj1208 splay伸展树的相关文章

Splay伸展树学习笔记

Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Tarjan 优点:每次查询会调整树的结构,使被查询频率高的条目更靠近树根. Tree Rotation 树的旋转是splay的基础,对于二叉查找树来说,树的旋转不破坏查找树的结构. Splaying Splaying是Splay Tree中的基本操作,为了让被查询的条目更接近树根,Splay Tree

【学时总结】◆学时&#183;VI◆ SPLAY伸展树

◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也-- ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋.双旋移动到某一个指定位置. 主要目的是将访问频率高的节点在不改变原顺序的前提下移动到尽量靠近根节点的位置,以此来解决同一个(相似)问题的多次查询. 但是在非降序查询每一个节点后,Splay 树会变为一条链,降低运算效率. ◇原理&细解 (1)旋转操作 二叉排序树必须满足 左儿子<根节点<右

Splay伸展树

伸展树,感觉对我这种菜鸟还是有些吃力,主要也是旋转的时候吧,把要查询的节点旋转到根节点,看网上是有两种方法,一是从上到下,一是从下到上.从上到下,是把树拆分成中树,左树和右树,始终保证目标节点在中树,不断向下把中树的节点添到右树或者左树,直到目标节点为中树的根,再将三树合并起来.另外一种从下到上,旋转操作类似AVL树中的旋转,但是判断好像不是很好写,我写的是从上到下的,另外删除也很巧妙,先把目标节点旋转到根,若此时左子树为空直接删除,否则找到左子树最右的节点当头,利用伸展树的特殊旋转就可以一步删

UVA 11922 Permutation Transformer —— splay伸展树

题意:根据m条指令改变排列1 2 3 4 - n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘贴到末尾可以用一次合并实现. 翻转可以采用在每个结点上做标记的方法,flip = 1意味着将这棵子树翻转,可以类似线段树用一个pushdown()实现标记向下传递. 可以发现当前排列就是伸展树的中序遍历序列.中序遍历打印结果即可. 注意代码中设置了虚拟首结点0的技巧. 代码如下: 1 #includ

Splay伸展树入门(单点操作,区间维护)

ps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是一颗二叉搜索树,刚刚开始学的时候找题hash数字的题先测板子... 后来那题被学长改了数据不能用平衡树测了...一道二分数字的题. 二叉搜索树的功能是,插入一个数字,在O(logn)的时间内找到它,并操作,插入删除等.但是可能会让二叉搜索树退化成链,复杂度达到O(n) 原文地址:https://www.c

codeforces 38G - Queue splay伸展树

题目 https://codeforces.com/problemset/problem/38/G 题意: 一些人按顺序进入队列,每个人有两个属性,地位$A$和能力$C$ 每个人进入时都在队尾,并最多可以和前一位互换$C$次,如果前一位的地位高于自己,则无法继续互换. 最终一次性输出整个队列 题解: splay维护动态的队列 可以用类似权值线段树的思维去考虑 对于每个点,保存其子节点的最大值,自身的值,与子树的大小 对于每次插入时,若能跨过整颗右子树与当前节点,则向左走,否则向右 可以保证整个子

splay伸展树模板

1 struct SplayTree 2 { 3 4 const static int maxn = 1e5 + 15; 5 6 int tot,root,ch[maxn][2], key[maxn], sz[maxn], lz[maxn], fa[maxn]; 7 8 void init( int x, int v = 0, int par = 0 ) 9 { 10 ch[x][0]=ch[x][1]=0, fa[x]= par, key[x] = v, sz[x] = 1; 11 } 12

HDU4453--Looploop (Splay伸展树)

Looploop XXX gets a new toy named Looploop. The toy has N elements arranged in a loop, an arrow pointing to one of the elements, and two preset parameters k1 and k2. Every element has a number on it. The figure above shows a Looploop of 6 elments. Le

树-伸展树(Splay Tree)

伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key[x]:如果y是x的右子树的一个结点,则key[y] >= key[x]. (02) 除了拥有二叉查找树的性质