[lnsyoj285/luoguP2596/ZJOI2006]书架

题意

维护一个长度为 \(n\) 的序列 \(a\),进行 \(m\) 次操作,操作包括:

  1. \(x\) 放置于序列开头;
  2. \(x\) 放置于序列末尾;
  3. \(x\) 与其前驱/后继交换;
  4. 查询 \(x\) 的下标 \(-1\)
  5. 查询下标为 \(x\) 的数

sol

维护序列,可以使用线段树或平衡树,本题使用平衡树更为简便。
介于已经学习过 Splay,本题使用 FHQ-Treap 解决。
(fhq佬%%%)
FHQ-Treap 通过将 BST 分裂与合并实现操作,且分裂与合并后该 BST 的 \(val\) 仍满足堆的性质。

分裂(Split)

FHQ-Treap 拥有两种分裂方式:按值分裂与按排名分裂
按值分裂时,会给定 \(qkey\),所有 \(\le qkey\) 的点都会被分裂至树 \(x\),所有 \(> qkey\) 的点都会被分裂至树 \(y\)
根据 BST 的定义,如果根节点的 \(key \le qkey\),说明根节点的左子树的所有节点都 \(\le key\),因此将根节点作为 \(x\) 的根,然后递归处理;同理,如果根节点的 \(key > qkey\),说明根节点的右子树的所有节点都 \(> key\),因此将根节点作为 \(y\) 的根,然后递归处理。
按值分裂适用于处理集合式问题,例如[lnsyoj118/luoguP3369]普通平衡树

代码

void split(int u, int key, int &x, int &y){
    if (!u) {
        x = y = 0;
        return ;
    }
    if (tr[u].key <= key) x = u, split(tr[u].r, key, tr[x].r, y);
    else y = u, split(tr[u].l, key, x, tr[y].l);
    pushup(u);
}

按排名分裂时,与按值分裂基本相同,但需要注意的是,如果递归遍历至右子树,查找的排名需要在原有基础上减去 \(lson.size+1\),即左子树和根节点中的节点数量
按排名分裂适用于处理序列式问题,例如本题和[lnsyoj119/luoguP3391]文艺平衡树
注意:按排名分裂调用 split 时,\(size\) 应为该树中的排名,而非序列中的排名
代码

void split(int u, int size, int &x, int &y){
    if (!u) {
        x = y = 0;
        return ;
    }
    if (tr[tr[u].l].size + 1 <= size) x = u, split(tr[u].r, size - tr[tr[u].l].size - 1, tr[x].r, y);
    else y = u, split(tr[u].l, size, x, tr[y].l);
    pushup(u);
}

合并(Merge)

由于合并后仍需要保持 \(val\) 二叉堆的性质,因此合并时需要依照 \(val\) 进行合并
具体地(以小根堆为例),如果树 \(x\) 根节点的\(val\)更小,则将树 \(x\) 根节点作为合并后树的根节点,其左子树即为树 \(x\) 的左子树,右子树为将树 \(x\) 的右子树与树 \(y\) 合并后得到的树;同理,如果树 \(y\) 根节点的\(val\)更小,则将树 \(y\) 根节点作为合并后树的根节点,其右子树即为树 \(y\) 的右子树,左子树为将树 \(y\) 的左子树与树 \(x\) 合并后得到的树。
代码

int merge(int x, int y){
    if (!x || !y) return x | y;
    if (tr[x].val <= tr[y].val) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}

值得注意的是,在进行分裂和合并操作之后,都需要进行上推操作来维护 \(size\)(和父节点)

说回本题。FHQ-Treap 并不支持根据 \(key\) 查询节点在序列中的位置,因此需要记录 \(pos\),来记录 \(key\) 对应的节点编号,再根据节点编号和父亲上推。具体操作是:根据BST的定义,该节点的左子树一定比该节点小,同时,当该节点是父节点的右子树时,父节点和左子树也一定比该节点小。
代码如下:

int get_rank(int u){
    int res = tr[tr[u].l].size + 1;
    while (u != root){
        if (tr[tr[u].fa].r == u) res += tr[tr[tr[u].fa].l].size + 1;
        u = tr[u].fa;
    }
    return res;
}

剩余的就是分裂与合并操作的组合了。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>

using namespace std;

const int N = 80005;

struct Node{
    int l, r;
    int key, val;
    int size;
    int fa;
}tr[N];

int idx;
int root;
int pos[N];
int n, m;

void pushup(int u){
    tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + 1;
    tr[tr[u].l].fa = tr[tr[u].r].fa = u;
}

int create(int key){
    tr[ ++ idx].key = key;
    tr[idx].val = rand();
    tr[idx].size = 1;
    pos[key] = idx;
    return idx;
}

void split(int u, int size, int &x, int &y){
    if (!u) {
        x = y = 0;
        return ;
    }
    if (tr[tr[u].l].size + 1 <= size) x = u, split(tr[u].r, size - tr[tr[u].l].size - 1, tr[x].r, y);
    else y = u, split(tr[u].l, size, x, tr[y].l);
    pushup(u);
}

int merge(int x, int y){
    if (!x || !y) return x | y;
    if (tr[x].val <= tr[y].val) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}

int get_rank(int u){
    int res = tr[tr[u].l].size + 1;
    while (u != root){
        if (tr[tr[u].fa].r == u) res += tr[tr[tr[u].fa].l].size + 1;
        u = tr[u].fa;
    }
    return res;
}

int get_key(int rank){
    int u = root;
    while (u){
        if (tr[tr[u].l].size >= rank) u = tr[u].l;
        else if (tr[tr[u].l].size + 1 == rank) return tr[u].key;
        else rank -= tr[tr[u].l].size + 1, u = tr[u].r;
    }
    return -1;
}

int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ){
        int t;
        scanf("%d", &t);
        root = merge(root, create(t));
    }

    int r1, r2, r3, r4;
    while (m -- ){
        r1 = r2 = r3 = r4 = 0;
        char op[10];
        int x;
        scanf("%s%d", op, &x);
        if (*op == 'T'){
            int p = get_rank(pos[x]);
            split(root, p - 1, r1, r2);
            split(r2, 1, r2, r3);
            root = merge(r2, merge(r1, r3));
        } else if (*op == 'B'){
            int p = get_rank(pos[x]);
            split(root, p - 1, r1, r2);
            split(r2, 1, r2, r3);
            root = merge(merge(r1, r3), r2);
        } else if (*op == 'I'){
            int t;
            scanf("%d", &t);
            if (!t) continue;
            int p = get_rank(pos[x]);
            if (t == 1){
                split(root, p - 1, r1, r2);
                split(r2, 1, r2, r3);
                split(r3, 1, r3, r4);
                root = merge(r1, merge(r3, merge(r2, r4)));
            } else{
                split(root, p - 2, r1, r2);
                split(r2, 1, r2, r3);
                split(r3, 1, r3, r4);
                root = merge(r1, merge(r3, merge(r2, r4)));
            }
        } else if (*op == 'A'){
            printf("%d\n", get_rank(pos[x]) - 1);
        } else if (*op == 'Q'){
            printf("%d\n", get_key(x));
        }
    }

    return 0;
}
posted @ 2024-07-07 14:25  是一只小蒟蒻呀  阅读(26)  评论(0编辑  收藏  举报