平衡树 Splay


分析

平衡二叉树
set/map 红黑树(代码长) == splay(代码适中,支持很多操作)
treap(有局限,一些操作做不了)
AVL
多叉树
B树 B+树

操作:
    左旋 
    右旋 (维护的是中序遍历不变)
             右旋
              ->
         z           z
        /           //
       y           x    
      / \         / \\
     x   c       A   y
    / \            // \
   A   B           B   C
   AxByc           AxByc
             左旋
              ->
   =线代表变得关系
   -代表不变的关系
   可以发现由于要维护中序遍历,最左和最右的关系是不变的
    插入
    查询
? 每操作一个节点 均将该节点旋转到树根
splay(x,k) 将点x旋转至点k下
1   
       z      y    x
      /      / \    \ 
     y      x   z    y
    /                 \
   x                   z    
        先转y     转x
    每转一次x往上走两格 直到某个点下面为止
2     z       z   x
     /       /   / \
    y       x   y   z
     \     /
      x   y
       先转x     转x

插入
1  找到x插入位置
   把x转到根
2  将一个序列插到y的后面
    ---------------
            y z
    2.1 找y的后继z 
    2.2 将y转到根       splay(y,0)
    2.3 将z转到y的下面  spaly(z,y)
   y
    \
     z
    /
  null(因为z和y之间没有数,所以z左子树为空) 
   2.4 将序列插入到z的左子树 
删除
   -----------------
     L-1 L   R R+1
   1 将L-1转到根节点
   2 将R+1转到L-1下面
   此时R+1的左子树=[L,R]
   3 将R+1的左儿子置为空Null

代码

/*made in mrd*/
#include
using namespace std;
const int N=2e5+10;
#define int long long
#define mem(a,b) memset(a,b,sizeof a)
#define fi first
#define se second
#define pii pair
#define pb push_back
int n,m;
struct node{
    int s[2],fa,v;
    int size,flag;
    void init(int _v,int _fa)
    {
        v=_v,fa=_fa;
        size=1;
    }
}tr[N];
int root,dex;
void pushup(int x)
{
    tr[x].size=tr[tr[x].s[0]].size+tr[tr[x].s[1]].size+1;
}
void pushdown(int x)
{
    // 如果当前子树需要翻转 48.34
    if (tr[x].flag)
    {
        //要翻转整颗子树,要先把左右两个儿子翻转,然后递归翻转左右两棵子树
        swap(tr[x].s[0], tr[x].s[1]);
        tr[tr[x].s[0]].flag ^= 1;//翻转标记往下传
        tr[tr[x].s[1]].flag ^= 1;//翻转标记往下传
        tr[x].flag = 0;//当前标记清空
    }
}
void rotate(int x)
{
    int y=tr[x].fa, z=tr[y].fa;
    int k=tr[y].s[1]==x;
    tr[z].s[tr[z].s[1]==y]=x,tr[x].fa=z;
    tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].fa=y;
    tr[x].s[k^1]=y;
    tr[y].fa=x;
    pushup(y),pushup(x);
}
void splay(int x, int k)
{
    while (tr[x].fa != k)
    {
        int y = tr[x].fa, z = tr[y].fa;
        if (z != k)
        // 如果是折线关系  ==  x是y的右/左儿子 且 y是z的左/右儿子  一0一1
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);//折线rotate两次x
            else rotate(y);//直线rotate 一次y一次x
        rotate(x);
    }
    if (!k) root = x;//如果k==0 根节点更新为x
}
void insert(int v)
{
    int u=root,p=0;
    while (u) p=u,u=tr[u].s[v>tr[u].v];
        u=++dex;
        if(p)
        {
            tr[p].s[v>tr[p].v]=u;
            
        }
        tr[u].init(v,p);
        splay(u,0);
}
int get_k(int k)
{
    int u=root;
    while(1)
    {
        pushdown(u);
        if(tr[tr[u].s[0]].size>=k) u=tr[u].s[0];
        else if(tr[tr[u].s[0]].size+1==k) return u;
        else k-=tr[tr[u].s[0]].size+1,u=tr[u].s[1];
    }
    return -1;
}
void output(int u)
{
    pushdown(u);
    // 如果u有左儿子 先递归输出左儿子
    if (tr[u].s[0]) output(tr[u].s[0]);
    // 如果u不是哨兵输出当前点
    if (tr[u].v >= 1 && tr[u].v <= n) printf("%d ", tr[u].v);
    // 如果u有右儿子 递归输出右儿子
    if (tr[u].s[1]) output(tr[u].s[1]);
}
signed main() {
cin>>n>>m;
    for (int i = 0; i <= n + 1; i ++ ) insert(i);//插入哨兵 0,n+1 防止L-1和R+1越界
    while (m -- )
    {
        int l, r;
        cin>>l>>r;
        //因为哨兵要翻转的从[L,R]变为[L+1,R+1] 则要找L和R+2作为L+1的前继和R+1的后继
        l = get_k(l), r = get_k(r + 2);
        // 左端点l转到根,右端点r转到左端点下面
        splay(l, 0), splay(r, l);
        // 只要将r的左子树[L+1,R+1]翻转
        tr[tr[r].s[0]].flag ^= 1;
    }
    output(root);
    return 0;
    return 0;
}