[lnsyoj285/luoguP2596/ZJOI2006]书架
题意
维护一个长度为 \(n\) 的序列 \(a\),进行 \(m\) 次操作,操作包括:
- 将 \(x\) 放置于序列开头;
- 将 \(x\) 放置于序列末尾;
- 将 \(x\) 与其前驱/后继交换;
- 查询 \(x\) 的下标 \(-1\);
- 查询下标为 \(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;
}