Splay

Splay

Basis

Definition

struct Miaksa
{
    int root, tot;
    struct SplayTree
    {
        int son[2]; // 左右子节点编号。0表示左子节点,1表示右子节点。
        int val; // 该点权值
        int sz; // 子树大小
        int cnt; // 相同权值的数的个数
        int fath; // 父节点编号
    }tr[N];
}mks;

Make new

int make_new(int v)
{
    tot++;
    tr[tot].val = v;
    tr[tot].sz = 1;
    tr[tot].cnt = 1;
    tr[tot].son[0] = tr[tot].son[1] = 0;
    return tot;
}

Push up

void pushup(int p)
{
    tr[p].sz = tr[tr[p].son[0]].sz + tr[tr[p].son[1]].sz + tr[p].cnt;
    // 当前节点的子树大小 = 左子树大小 + 右子树大小 + 当前节点对应权值的数量
}

Rotate

void rotate(int x) // 旋转。下面均为白话文。
{
    int y = tr[x].fath;
    int z = tr[y].fath;
    int a = tr[y].son[1] == x; 
    int b = tr[z].son[1] == y;
    tr[z].son[b] = x;
    tr[x].fath = z;
    tr[y].son[a] = tr[x].son[a ^ 1];
    tr[tr[x].son[a ^ 1]].fath = y;
    tr[x].son[a ^ 1] = y;
    tr[y].fath = x;
    pushup(y); // 注意 pushup 的顺序!
    pushup(x);
}

Splay

void splay(int x, int goal) // 将 x 旋到目标节点 goal 的子节点处。
{
    while(tr[x].fath != goal)
    {
        int y = tr[x].fath;
        int z = tr[y].fath;
        if(z != goal) // 如果 x 的祖父 z 不是 goal,则一次循环内可以进行两次 rotate 操作,使 x 爬升两层
            (tr[z].son[0] == y) ^ (tr[y].son[0] == x) ? rotate(x) : rotate(y);
        	// 根据 x, y, z 是否共线分为两种旋转方式,是为了保证正确的时间复杂度
        rotate(x); // 一次循环内必然要将 x 旋上去一次
    }
    if(!goal) root = x; // 将 x 旋到根节点
}

Find

这是一个非常重要且易错的函数。

inline void find(int x) // 找到权值为 x 的节点,并旋到根节点(如果找不到,会操作其前驱或后继)
{
    int u = root;
    if(!u) return; // 空树
    // 之后的函数中频繁用 x > tr[u].val 判断跳左儿子还是右儿子,十分方便
    while(tr[u].son[x > tr[u].val] && x != tr[u].val)
        u = tr[u].son[x > tr[u].val];
    //找到了权值 x 后 u 会跳到对应的节点,找不到权值 x 则 u 会跳到 x 的前驱或后继。
    splay(u, 0); // 之后的几个函数,每次调用后,都要向这样做一次正确复杂度的保证
}

Insert

inline void insert(int x) // 插入一个权值为 x 的数,并把对应节点旋到根节点
{
    int u = root, fa = 0; // fa 用于平衡树中没有权值 x 的情况时,将新建节点与平衡树上的节点 fa 相连
    while(u && tr[u].val != x)
    {
        fa = u; // u 往下跳,同时记录下一步跳到的 u 的父节点 fa
        u = tr[u].son[x > tr[u].val];
    }
    if(u) tr[u].cnt++; // 找到了权值为 x 的节点,增加数量
    else // 平衡树上没有权值为 x 的节点
    {
        u = make_new(x);
        if(fa)
            tr[fa].son[x > tr[fa].val] = u;
        tr[u].fath = fa;
    }
    splay(u, 0);
}

Get the last

inline int get_lst(int x) // 查找权值 x 的前驱节点。前驱定义为最大的 < x 的数。
{
    find(x);
    int u = root;
    if(tr[u].val < x) // find(x) 恰好跳到了前驱
        return u;
    u = tr[u].son[0]; // 在 < tr[u].val 的值域中查找最大的权值
    				//此时 tr[u].val >= x,则 u 的左子树中的任意节点权值一定 < x。(tr[u].val > x 时说明树中没有权值 x)
    while(tr[u].son[1]) // 一直向右跳即可找到最值
        u = tr[u].son[1];
    return u;
}

Get the next

inline int get_nxt(int x) // 查找权值 x 的后继节点,原理与 get_lst 相同
{
    find(x);
    int u = root;
    if(tr[u].val > x) 
        return u;
    u = tr[u].son[1];
    while(tr[u].son[0])
        u = tr[u].son[0];
    return u;
}

题外话:

对于前驱和后继的查询,笔者还尝试了这样一种方法:

inline int get_lst(int x)
{
    int u = root, res = 0;
    while(u)
    {
        if(tr[u].val < x) res = u;
        u = tr[u].son[tr[u].val < x];
    }
    splay(res, 0);
    return res;
}
inline int get_nxt(int x)
{
    int u = root, res = 0;
    while(u)
    {
        if(tr[u].val > x) res = u;
        u = tr[u].son[tr[u].val <= x];
    }
    splay(res, 0);
    return res;
}

其原理是从根向下跳,在不确定的探索中尽可能地靠近 x,并用途中满足条件的节点更新 res

由于每次都尝试向 x 靠近,因此新的满足条件的节点一定更优。

最后将 res 旋到根处。

这样的做法是答案正确的,但是时间复杂度 可能 有问题。(是可以过模板的)(下面可能在说胡话)

问题就在于,我们一直搜到底,却只会 splayres 往上的部分。

res 可能只是中间的某个节点:因此不能保证时间复杂度。

Remove

inline void remove(int x) // 删除一个权值为 x 的数
{
    int lst = get_lst(x); // x 的前驱节点
    int nxt = get_nxt(x); // x 的后继节点
    splay(lst, 0); // 把 lst 作为根节点
    splay(nxt, lst); // 将 nxt 作为 lst 的父节点,则此时 nxt 一定为 lst 的右节点。
    				// 由于 x > tr[lst].val,所以 x 在 nxt 子树内;
    				//又因为 x 是 nxt 子树范围内唯一可以 < tr[nxt].val 的数,因此权值 x 对应节点一定是 nxt 的左子节点
    int u = tr[nxt].son[0]; // 找到权值 x 对应节点 u
    if(tr[u].cnt > 1)
    {
        tr[u].cnt--;
        splay(u, 0);
    }
    else // 权值数量为 1
        tr[nxt].son[0] = 0; // 直接删去该节点。由之前的分析还可知,u 一定是叶节点,所以删除时只需要修改其父节点 nxt 的信息。
}

Get rank by value

权值 \(x\) 的排名定义为比 \(x\) 小的数的个数 \(+1\)

这也是个初学时易错的函数。

inline int get_rank_by_val(int x) // 查询权值 x 的排名
{
    find(x);
    return tr[tr[root].son[0]].sz + (tr[root].val < x ? tr[root].cnt : 0) + 1;
}

也可以先插入 x 后再查询:

inline int get_rank_by_val(int x)
{
    insert(x);
    find(x);
    int res = tr[tr[root].son[0]].sz + 1;
    remove(x);
    return res;
}

Get value by rank

inline int get_val_by_rank(int x)
{
    int u = root;
    if(tr[u].sz < x) // 排名超出了数的个数,找不到
        return 0;
    while(1) // 写过线段树二分或其他平衡树的话,这里就很好理解了
    {
        int v = tr[u].son[0];
        if(x > tr[v].sz + tr[u].cnt)
        {
            x -= tr[v].sz + tr[u].cnt;
            u = tr[u].son[1];
        }
        else
        {
            if(tr[v].sz >= x)
                u = v;
            else return tr[u].val;
        }
    }
}

Final Code

#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5;
const int INF = INT_MAX;
struct Mikasa
{
    int root, tot;
    struct SplayTree
    {
        int son[2];
        int val;
        int sz, cnt;
        int fath;
    }tr[N];
    int make_new(int v)
    {
        tot++;
        tr[tot].val = v;
        tr[tot].sz = 1;
        tr[tot].cnt = 1;
        tr[tot].son[0] = tr[tot].son[1] = 0;
        return tot;
    }
    void pushup(int p)
    {
        tr[p].sz = tr[tr[p].son[0]].sz + tr[tr[p].son[1]].sz + tr[p].cnt;
    }
    void rotate(int x)
    {
        int y = tr[x].fath;
        int z = tr[y].fath;
        int a = tr[y].son[1] == x;
        int b = tr[z].son[1] == y;
        tr[z].son[b] = x;
        tr[x].fath = z;
        tr[y].son[a] = tr[x].son[a ^ 1];
        tr[tr[x].son[a ^ 1]].fath = y;
        tr[x].son[a ^ 1] = y;
        tr[y].fath = x;
        pushup(y), pushup(x);
    }
    void splay(int x, int goal)
    {
        while(tr[x].fath != goal)
        {
            int y = tr[x].fath;
            int z = tr[y].fath;
            if(z != goal)
                (tr[z].son[0] == y) ^ (tr[y].son[0] == x) ? rotate(x) : rotate(y);
            rotate(x);
        }
        if(!goal) root = x;
    }
    inline void find(int x)
    {
        int u = root;
        if(!u) return;
        while(tr[u].son[x > tr[u].val] && x != tr[u].val)
            u = tr[u].son[x > tr[u].val];
        splay(u, 0);
    }
    inline void insert(int x)
    {
        int u = root, fa = 0;
        while(u && tr[u].val != x)
        {
            fa = u;
            u = tr[u].son[x > tr[u].val];
        }
        if(u) tr[u].cnt++;
        else 
        {
            u = make_new(x);
            if(fa)
                tr[fa].son[x > tr[fa].val] = u;
            tr[u].fath = fa;
        }
        splay(u, 0);
    }
    inline int get_lst(int x)
    {
        find(x);
        int u = root;
        if(tr[u].val < x)
            return u;
        u = tr[u].son[0];
        while(tr[u].son[1])
            u = tr[u].son[1];
        splay(u, 0);
        return u;
    }
    inline int get_nxt(int x)
    {
        find(x);
        int u = root;
        if(tr[u].val > x)
            return u;
        u = tr[u].son[1];
        while(tr[u].son[0])
            u = tr[u].son[0];
        splay(u, 0);
        return u;
    }
    inline void remove(int x)
    {
        int lst = get_lst(x);
        int nxt = get_nxt(x);
        splay(lst, 0);
        splay(nxt, lst);
        int u = tr[nxt].son[0];
        if(tr[u].cnt > 1)
        {
            tr[u].cnt--;
            splay(u, 0);
        }
        else
            tr[nxt].son[0] = 0;
    }
    inline int get_rank_by_val(int x)
    {
        find(x);
        return tr[tr[root].son[0]].sz + (tr[root].val < x ? tr[root].cnt : 0) + 1;;
    }
    inline int get_val_by_rank(int x)
    {
        int u = root;
        if(tr[u].sz < x)
            return 0;
        while(1)
        {
            int v = tr[u].son[0];
            if(x > tr[v].sz + tr[u].cnt)
            {
                x -= tr[v].sz + tr[u].cnt;
                u = tr[u].son[1];
            }
            else
            {
                if(x <= tr[v].sz)
                    u = v;
                else 
                {
                    splay(u, 0);
                    return tr[u].val;
                }
            }
        }
    }
}mks;
int n, Q, lastans, ans;
int main()
{
    mks.insert(INF);
    mks.insert(-INF);
    scanf("%d %d", &n, &Q);
    for(int i = 1; i <= n; ++i)
    {
        int x; scanf("%d", &x);
        mks.insert(x);
    }
    while(Q--)
    {
        int op, x;
        scanf("%d %d", &op, &x);
        x ^= lastans;
        if(op == 1) 
            mks.insert(x);
        if(op == 2) 
            mks.remove(x);
        if(op == 3)
            lastans = mks.get_rank_by_val(x) - 1;
        if(op == 4)
            lastans = mks.get_val_by_rank(x + 1);
        if(op == 5)
            lastans = mks.tr[mks.get_lst(x)].val;
        if(op == 6)
            lastans = mks.tr[mks.get_nxt(x)].val;
        if(op > 2) ans ^= lastans;
    }
    printf("%d\n", ans);
    return 0;
}

Time Complexity

这一部分感谢 cool_milo 的笔记对我的帮助。

由于 splay 操作是对其他函数的时间复杂度的保证,因此我们需先分析 splay 函数的时间复杂度。

设旋转操作 rotate 的复杂度为 \(k\)\(sz_x\) 为以 \(x\) 为根的子树大小,势能函数 \(\phi(x) = k\log{sz_x}\)。这里及以下的 \(\log\) 运算均以 \(2\) 为底。记 \(\Phi = \sum{\phi_x}\)。注意势能函数是我们人为定义的,因此为了更好地分析复杂度,有必要有一个 \(k\) 的倍数。

对于一棵不平衡的树,\(\Phi\) 会变大。

我们需要计算 \(\Delta{\Phi}\),定义 \(\Delta{\Phi}\) 表示 rotate 操作导致的 势能变化与所用时间的总和。记 \(\phi^{'}(x)\)rotate 后的势能,\(sz^{'}_x\)rotate 后的子树大小。

rotate 要分三种情况。现有节点 \(x\)\(y\)\(x\) 的父节点,\(z\)\(y\) 的父节点。

\(A, B, C, D\) 均表示子树而非节点。

rotate(x)

\(z\) 没有关系。

\[\begin{aligned} \Delta{\Phi} &= k + \phi^{'}(y) - \phi(y) + \phi^{'}(x) - \phi(x) \\ &= k + \phi^{'}(y) - \phi(x) \\ &\le k + \phi^{'}(x) - \phi(x) \end{aligned} \]

rotate(x), rotate(x)

注意看首尾两个状态,第二张图是过程。

\[\begin{aligned} \Delta{\Phi} &= k + \phi^{'}(x) - \phi(x) + \phi^{'}(y) - \phi(y) + \phi^{'}(z) - \phi(z) \\ &= k - \phi(x) + \phi^{'}(y) - \phi(y) + \phi^{'}(z) \\ &= k + \phi^{'}(z) + k\left(\log{sz^{'}_y} - \log{sz_y} - \log{sz_x} \right) \\ &= k + \phi^{'}(z) + k\log{\frac{sz^{'}_y}{sz_x \times sz_y}} \\ & \because sz^{'}_y < sz^{'}_x, sz_y > sz_x, \\ & \therefore \frac{sz^{'}_y}{sz_y} < \frac{sz^{'}_x}{sz_x}, \\ \therefore \Delta{\Phi} & \le k + \phi^{'}(z) + k\log{\frac{sz^{'}_x}{(sz_x)^2}} = k + \phi^{'}(x) + \phi^{'}(z) - 2\phi(x) \\ \end{aligned} \]

通过分式比较大小来放缩,似乎效果不是很好(其实是我不会后面怎么推了)。

不妨不用势能函数的原始定义,而是根据 \(\phi(x)\) 关于 \(sz_x\) 单增的性质放缩。(\(\phi^{'}(x)\) 同理)

\(sz_{x} < sz_{y}\),所以 \(\phi(x) < \phi(y)\),所以由上式的第二步可以直接得出:

\[\Delta{\Phi} \le k + \phi^{'}(y) + \phi^{'}(z) - 2\phi(x) \]

\(\phi^{'}(y) < \phi^{'}(x)\),所以后面一种放缩方式更优。

Lemma 1\(\phi^{'}(y) + \phi^{'}(z) - 2\phi^{'}(x) \le -1\)

\[\begin{aligned} &\,\,\,\,\,\,\, \phi^{'}(y) + \phi^{'}(z) - 2\phi^{'}(x) \\ &= k\log{\frac{sz^{'}_y \times sz^{'}_z}{(sz^{'}_x)^2}} \\ &\le k\log{\frac{sz^{'}_y \times sz^{'}_z}{(sz^{'}_y + sz^{'}_z)^2}} \\ &\le k\log{\frac{sz^{'}_y \times sz^{'}_z}{2\times sz^{'}_y \times sz^{'}_z}} = -k \end{aligned} \]

由引理 \(1\)

\[-k + 2\phi^{'}(x) - \phi^{'}(y) - \phi^{'}(z) \ge 0 \\ \]

将原式加上一个非负数的结果不小于其本身,利用这一点进行放缩,即:

\[\begin{aligned} \Delta{\Phi} &\le k + \phi^{'}(y) + \phi^{'}(z) - 2\phi(x) \\ & \le k + \phi^{'}(y) + \phi^{'}(z) - 2\phi(x) -k + 2\phi^{'}(x) - \phi^{'}(y) - \phi^{'}(z) \\ &= 2\left( \phi^{'}(x) - \phi(x) \right) \end{aligned} \]

rotate(y), rotate(x)

注意看首尾两个状态,第二张图是过程。

\[\begin{aligned} \Delta{\Phi} &= k + \phi^{'}(x) - \phi(x) + \phi^{'}(y) - \phi(y) + \phi^{'}(z) - \phi(z) \\ &= k - \phi(x) + \phi^{'}(y) - \phi(y) + \phi^{'}(z) \\ &\le k + \phi^{'}(z) + \phi^{'}(y) - 2\phi(x) \\ &\le k + \phi^{'}(z) + \phi^{'}(x) - 2\phi(x) \end{aligned} \]

Lemma 2\(\phi(x) + \phi^{'}(z) - 2\phi^{'}(x) \le -1\)

\[\begin{aligned} &\,\,\,\,\,\,\, \phi(x) + \phi^{'}(z) - 2\phi^{'}(x) \\ &= k\log{\frac{sz_x \times sz^{'}_z}{(sz^{'}_x)^2}} \\ &\le k\log{\frac{sz_x \times sz^{'}_z}{(sz_x + sz^{'}_z)^2}} \\ &\le k\log{\frac{sz_x \times sz^{'}_z}{2\times sz_x \times sz^{'}_z}} = -k \end{aligned} \]

同样的,将原式加上非负数 $-1 + 2\phi^{'}(x) - \phi^{'}(z) - \phi(x) $ 以进行放缩:

\[\begin{aligned} \Delta{\Phi} & \le k + \phi^{'}(z) + \phi^{'}(x) - 2\phi(x) \\ &\le k + \phi^{'}(z) + \phi^{'}(x) - 2\phi(x) - k + 2\phi^{'}(x) - \phi^{'}(z) - \phi(x) \\ &= 3\left( \phi^{'}(x) - \phi(x) \right) \end{aligned} \]

然后分析 splay 的时间复杂度。对于从 \(x\) 旋到根 \(root\) 的过程,我们记 \(\Phi\) 的总变化量为 \(W\)

对于双旋,取 \(\Delta{\Phi} \le \max\left( 2\left( \phi^{'}(x) - \phi(x) \right), 3\left( \phi^{'}(x) - \phi(x) \right) \right) = 3\left( \phi^{'}(x) - \phi(x) \right)\),总计贡献 \(3\left( \phi(root) - \phi(x) \right) \le 3k\log{n}\)

对于单旋,每次 splay 操作中至多执行一次,所以会贡献 \(k + \Delta{\phi} \le k + k\log{n}\)。并且后面的 \(k\log{n}\) 抵满的几率很小。

\[W \le k + 4k\log{n} = O(\log{n}) \]

我们将势能变化量与时间消耗合起来考虑为 \(W\),但我们想要知道时间消耗是多少,就必须把势能变化量减掉。

考虑链和完全二叉树两种极端情况。 \(\Phi_{max} = k\sum\limits_{i = 1}^{n}\log{i} = O(n\log{n})\)\(\Phi_{min} = k\sum\limits_{i = 1}^{\log{n}}\log{(2^i\times i)} = k\sum\limits_{i = 1}^{\log{n}}i\log{i}\)

不会算

事实上,考虑单次 splay 操作也并不是一件明智的事情。我们再扩大考虑范围,考虑多次 splay 的总时间复杂度,此时总势能变化量的数量级是 \(O(n\log{n})\) 的,假设操作次数 \(Q\)\(n\) 同级,那么 \(W\) 的总和也是 \(O(n\log{n})\) 的,于是多次 splay 的总时间复杂度为 \(O(n\log{n})\)

考虑 splay 操作中层数与 rotate 的操作次数 \(t\) 是同级的,而操作次数 \(t\) 可以与 splay 的总时间复杂度相关联,而 rotate 的复杂度是 \(O(1)\) 的,可得 \(t\) 的数量级是 \(O(n\log{n})\),均摊到 \(Q\) 次询问,树的平均深度处于 \(O(\log{n})\) 级别,则其他函数的均摊时间复杂度也可以确定在 \(O(\log{n})\) 级别。由此分析,特别注意:对于那些从根节点往下跳的操作,每次到达最终节点 \(u\) 后都要进行 splay(u, 0)

文艺平衡树

考察区间打标记。

考虑用下标作为在平衡树上排序的权值,此时排名为 k 的数即为序列中的第 k 个数。

注意这样一件事:插入到平衡树上的权值仍然为原序列中的权值,但我们查到的排名需要是实时更新的排名,即原序列中的下标。

但最初插入到平衡树上的数已经是有了一定顺序的,这个时候有两种处理方法:

  • 把原序列的排名和对应权值一起插进平衡树,用排名排序,构建出最初形态的平衡树;

    此后,原序列排名被弃用,之后序列的顺序由平衡树的形态不断变化而随之变化。

  • 中序遍历直接建树。

那么显然是第二种方法更优。

另外,这里可能存在权值相同的数,我们 不能 将它们合并到一个节点,即每个节点的 cnt 均为 \(1\),所以可以不用记录这个量。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int INF = INT_MAX;
int n, Q, a[N];
struct Mikasa
{
    int root, tot;
    struct Nagisa
    {
        int rev;
    };
    struct SplayTree
    {
        int son[2];
        int val;
        int sz;
        int fath;
        Nagisa dango;
    }tr[N];
    void pushup(int p)
    {
        tr[p].sz = tr[tr[p].son[0]].sz + tr[tr[p].son[1]].sz + 1;
    }
    int build(int l, int r, int fa) // 类比替罪羊树的重构,注意替罪羊树是对节点 cur[mid] 的信息处理
    {
        if(l > r) return 0;
        int mid = l + (r - l) / 2;
        tr[mid].val = a[mid];
        tr[mid].fath = fa;
        tr[mid].sz = 1;
        tr[mid].son[0] = build(l, mid - 1, mid);
        tr[mid].son[1] = build(mid + 1, r, mid);
        pushup(mid);
        return mid;
    }
    void pushdown(int p)
    {
        if(tr[p].dango.rev)
        {
            tr[tr[p].son[0]].dango.rev ^= 1;
            tr[tr[p].son[1]].dango.rev ^= 1;
            swap(tr[p].son[0], tr[p].son[1]);
            tr[p].dango.rev = 0;
        }
    }
    void rotate(int x)
    {
        int y = tr[x].fath;
        int z = tr[y].fath;
        int a = tr[y].son[1] == x;
        int b = tr[z].son[1] == y;
        tr[z].son[b] = x;
        tr[x].fath = z;
        tr[y].son[a] = tr[x].son[a ^ 1];
        tr[tr[x].son[a ^ 1]].fath = y;
        tr[x].son[a ^ 1] = y;
        tr[y].fath = x;
        pushup(y), pushup(x);
    }
    void splay(int x, int goal)
    {
        while(tr[x].fath != goal)
        {
            int y = tr[x].fath;
            int z = tr[y].fath;
            if(z != goal)
                (tr[z].son[0] == y) ^ (tr[y].son[0] == x) ? rotate(x) : rotate(y);
            rotate(x);
        }
        if(!goal) root = x;
    }
    inline int get_kth(int u, int x) // 注意返回的是排名为 k 的节点而非权值
    {
        pushdown(u);
        if(x > tr[tr[u].son[0]].sz && x <= tr[tr[u].son[0]].sz + 1)
            { splay(u, 0); return u; }
        if(x <= tr[tr[u].son[0]].sz)
            return get_kth(tr[u].son[0], x);
        return get_kth(tr[u].son[1], x - tr[tr[u].son[0]].sz - 1);
    }
    inline void reverse(int l, int r) // actually [l + 1, r + 1]
    {
        l = get_kth(root, l);
        r = get_kth(root, r + 2);
        splay(l, 0);
        splay(r, l);
        tr[tr[tr[l].son[1]].son[0]].dango.rev ^= 1;
    }
    void print(int u)
    {
        if(!u) return;
        pushdown(u);
        print(tr[u].son[0]);
        if(tr[u].val != INF)
            printf("%d ", tr[u].val);
        print(tr[u].son[1]);
    }
}mks;
int main()
{
    scanf("%d %d", &n, &Q);
    for(int i = 2; i <= n + 1; ++i)
        a[i] = i - 1;
    a[1] = a[n + 2] = INF;
    mks.root = mks.build(1, n + 2, 0);
    while(Q--)
    {
        int l, r;
        scanf("%d %d", &l, &r);
        mks.reverse(l, r);
    }
    mks.print(mks.root);
    return 0;
}
posted @ 2023-01-28 16:38  Schucking_Sattin  阅读(30)  评论(0)    收藏  举报