洛谷 P3850 [TJOI2007]书架
Description
Solution
无旋treap \(fhq-treap\)
关于 \(fhq-treap\) 不会的同学可以去看我的博客 浅谈 fhq-treap(无旋treap)
一道经典的按区间大小分裂的题。
这道题中,我们要稍微修改 \(split\) 函数。
以前我们写过的大部分题目都是按数值来分裂的,这次我们换一种分裂参数。
我们以子树大小来分裂,这是为什么呢?
因为我们插入,查询时都是输入的排名,对于一棵平衡树来说,一个节点的左子树中的值都是小于它的数,右子树中的值都是大于它的数。
因此按子树大小来分裂刚好可以满足条件。
再说一些细节。
-
输入的都是字符串,这个好说,我们给它映射一下,映射到一个数字上即可。
-
对于刚开始的就有的 \(n\) 本书,因为排名是从 0 开始的,所以我们插入时要 \(insert(i - 1, cnt)\)
(\(cnt\) 就是一个字符串映射之后的数字)
-
对于后来插入的 \(m\) 本书,思考一下,假设插入到第 \(x\) 位上,那么它就会变成第 \(x\) 本书,所以 \(insert(x, cnt)\)
结合代码理解一下吧
Code
#include <bits/stdc++.h>
#define ls(x) t[x].ch[0]
#define rs(x) t[x].ch[1]
using namespace std;
const int N = 2e5;
struct Treap{
int ch[2], val, siz, wei;
}t[N];
int n, m, q, tot, root, cnt;
int a, b, c;
char s[N][15];
inline void pushup(int x){
t[x].siz = t[ls(x)].siz + t[rs(x)].siz + 1;
}
inline void split(int x, int k, int &a, int &b){
if(!x){
a = b = 0;
return;
}
if(k > t[ls(x)].siz){ //分裂时注意按子树大小分裂
a = x;
split(rs(a), k - t[ls(x)].siz - 1, rs(a), b);
}else{
b = x;
split(ls(b), k, a, ls(b));
}
pushup(x);
}
inline int merge(int x, int y){
if(!x || !y) return x + y;
if(t[x].wei <= t[y].wei){
rs(x) = merge(rs(x), y);
pushup(x);
return x;
}else{
ls(y) = merge(x, ls(y));
pushup(y);
return y;
}
}
inline int newnode(int k){
t[++tot].val = k, t[tot].siz = 1, t[tot].wei = rand();
return tot;
}
inline void insert(int k, int x){ //插入这里传两个参,一个是插到第几位,另一个是编号
split(root, k, a, b);
root = merge(a, merge(newnode(x), b));
}
inline int query(int k){
split(root, k, a, b);
split(b, 1, b, c);
int rank = t[b].val;
merge(a, merge(b, c));
return rank;
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%s", s[++cnt]);
insert(i - 1, cnt); //插入道第 i - 1 位上
}
scanf("%d", &m);
for(int i = 1; i <= m; i++){
int x;
scanf("%s %d", s[++cnt], &x);
insert(x, cnt);
}
scanf("%d", &q);
while(q--){
int x;
scanf("%d", &x);
printf("%s\n", s[query(x)]);
}
return 0;
}