FHQ Treap
按值分裂
/* 按值x分裂Treap:将树u分裂为<=x的树l和>x的树r */
void split(int u, int x, int& l, int& r) {if (!u) { l = r = 0; return; } // 空树直接返回if (t[u].val <= x) { // 当前节点值<=x,应放入左树l = u; // 当前节点作为左树的根split(t[u].rs, x, t[u].rs, r); // 递归处理右子树} else { // 当前节点值>x,应放入右树r = u; // 当前节点作为右树的根split(t[u].ls, x, l, t[u].ls); // 递归处理左子树}pushup(u); // 更新当前节点子树大小
}/* 合并两棵Treap:合并左树l和右树r */
int merge(int l, int r) {if (!l || !r) return l + r; // 任意一棵树为空则返回另一棵// 按照优先级合并(维护堆性质)if (t[l].pri > t[r].pri) { // 左树根优先级更高t[l].rs = merge(t[l].rs, r); // 将右树合并到左树的右子树pushup(l); // 更新左树大小return l; // 返回合并后的左树} else { // 右树根优先级更高t[r].ls = merge(l, t[r].ls); // 将左树合并到右树的左子树pushup(r); // 更新右树大小return r; // 返回合并后的右树}
}/* 插入值x */
void add(int x) {int l, r;split(root, x, l, r); // 按x分裂原树newnode(x); // 创建新节点root = merge(merge(l, cnt), r); // 合并左树、新节点、右树
}/* 删除值x */
void del(int x) {int l, r, p;split(root, x, l, r); // 按x分裂原树split(l, x - 1, l, p); // 在左树中按x-1分裂,得到等于x的节点pp = merge(t[p].ls, t[p].rs); // 合并p的左右子树(相当于删除p)root = merge(merge(l, p), r); // 合并所有部分
}/* 查询x的排名(比x小的数的个数+1) */
int Rank(int x) {int l, r;split(root, x - 1, l, r); // 按x-1分裂,左树包含所有<x的值int res = t[l].siz + 1; // 排名=左树大小+1root = merge(l, r); // 合并回原树return res;
}/* 查询第k小的数(递归实现) */
int kth(int u, int k) {if (k == t[t[u].ls].siz + 1) return t[u].val; // 找到目标节点if (k > t[t[u].ls].siz + 1) // 在右子树中查找return kth(t[u].rs, k - (t[t[u].ls].siz + 1));return kth(t[u].ls, k); // 在左子树中查找
}/* 查询前驱(type=0)或后继(type=1) */
int nex(int x, int type) {int l, r;split(root, x - !type, l, r); // type=0分裂为<=x-1和>x-1;type=1分裂为<=x和>xint res;if (!type) res = kth(l, t[l].siz); // 前驱是左树的最大值else res = kth(r, 1); // 后继是右树的最小值root = merge(l, r); // 合并回原树return res;
}
按排名分裂
rank与kth代码一样
void split(int u, int k, int& l, int& r) {if (!u) { l = r = 0; return; }if (t[t[u].ls].siz + 1 <= k) { // 当前节点属于左树l = u;split(t[u].rs, k - t[t[u].ls].siz - 1, t[u].rs, r);}else { // 当前节点属于右树r = u;split(t[u].ls, k, l, t[u].ls);}pushup(u);
}/* 合并两棵树 */
int merge(int l, int r) {if (!l || !r) return l + r;if (t[l].pri > t[r].pri) {t[l].rs = merge(t[l].rs, r);pushup(l);return l;}else {t[r].ls = merge(l, t[r].ls);pushup(r);return r;}
}/* 插入值x */
void insert(int x) {int l, r;split(root, Rank(x) - 1, l, r); // 按排名分裂newnode(x);root = merge(merge(l, cnt), r);
}/* 删除值x */
void del(int x) {int l, r, p;split(root, Rank(x) - 1, l, r);split(r, 1, p, r); // 分离出要删除的节点root = merge(l, r); // 跳过被删除节点
}/* 查询前驱(type=0)或后继(type=1) */
int nex(int x, int type) {int l, r;split(root, Rank(x) - !type, l, r);int res = type ? kth(1) : kth(t[l].siz);root = merge(l, r);return res;
}
可持久化
P5055 【模板】可持久化文艺平衡树
题目描述
您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):
- 在第 p p p 个数后插入数 x x x。
- 删除第 p p p 个数。
- 翻转区间 [ l , r ] [l,r] [l,r],例如原序列是 { 5 , 4 , 3 , 2 , 1 } \{5,4,3,2,1\} {5,4,3,2,1},翻转区间 [ 2 , 4 ] [2,4] [2,4] 后,结果是 { 5 , 2 , 3 , 4 , 1 } \{5,2,3,4,1\} {5,2,3,4,1}。
- 查询区间 [ l , r ] [l,r] [l,r] 中所有数的和。
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 4 4 4 即保持原版本无变化),新版本即编号为此次操作的序号。
本题强制在线。
输入格式
第一行包含一个整数 n n n,表示操作的总数。
接下来 n n n 行,每行前两个整数 v i , o p t i v_i, \mathrm{opt}_i vi,opti, v i v_i vi 表示基于的过去版本号( 0 ≤ v i < i 0 \le v_i < i 0≤vi<i), o p t i \mathrm{opt}_i opti 表示操作的序号( 1 ≤ o p t i ≤ 4 1 \le \mathrm{opt}_i \le 4 1≤opti≤4)。
若 o p t i = 1 \mathrm{opt}_i=1 opti=1,则接下来两个整数 p i , x i p_i, x_i pi,xi,表示操作为在第 p i p_i pi 个数后插入数 x x x 。
若 o p t i = 2 \mathrm{opt}_i=2 opti=2,则接下来一个整数 p i p_i pi,表示操作为删除第 p i p_i pi 个数。
若 o p t i = 3 \mathrm{opt}_i=3 opti=3,则接下来两个整数 l i , r i l_i, r_i li,ri,表示操作为翻转区间 [ l i , r i ] [l_i, r_i] [li,ri]。
若 o p t i = 4 \mathrm{opt}_i=4 opti=4,则接下来两个整数 l i , r i l_i, r_i li,ri,表示操作为查询区间 [ l i , r i ] [l_i, r_i] [li,ri] 的和。
强制在线规则:
令当前操作之前的最后一次 4 4 4 操作的答案为 l a s t a n s lastans lastans(如果之前没有 4 4 4 操作,则 l a s t a n s = 0 lastans=0 lastans=0)。
则此次操作的 p i , x i p_i,x_i pi,xi 或 l i , r i l_i,r_i li,ri 均按位异或上 l a s t a n s lastans lastans 即可得到真实的 p i , x i p_i,x_i pi,xi 或 l i , r i l_i,r_i li,ri。
输出格式
对于每个序号为 4 4 4 的查询操作,输出一行一个数表示区间的和。
// 复制树节点,只需复制根节点相关信息
int clone(int u) {int ret = new_node(0); // 新建一个节点t[ret] = t[u]; // 复制节点u的信息到新节点return ret;
}
// 更新节点信息,包括子树大小和权值和
void Update(int u) {t[u].size = t[t[u].ls].size + t[t[u].rs].size + 1;t[u].sum = t[t[u].ls].sum + t[t[u].rs].sum + t[u].val;
}
// 下传懒标记
void push_down(int u) {if (!t[u].lazy) return; // 若无懒标记,直接返回if (t[u].ls != 0) t[u].ls = clone(t[u].ls); // 复制左子树if (t[u].rs != 0) t[u].rs = clone(t[u].rs); // 复制右子树swap(t[u].ls, t[u].rs); // 交换左右子树t[t[u].ls].lazy ^= 1; // 左子树懒标记取反t[t[u].rs].lazy ^= 1; // 右子树懒标记取反t[u].lazy = 0; // 清空当前节点懒标记
}
// 按排名分裂操作
void Split(int u, int x, int &L, int &R) {if (u == 0) {L = R = 0; return; } // 若节点为空,左右子树设为0并返回push_down(u); // 下传懒标记if (t[t[u].ls].size + 1 <= x) { // 第x个数在u的右子树上L = clone(u); // 复制当前节点作为左子树Split(t[L].rs, x - t[t[u].ls].size - 1, t[L].rs, R); // 递归分裂右子树Update(L); // 更新左子树信息}else {R = clone(u); // 复制当前节点作为右子树Split(t[R].ls, x, L, t[R].ls); // 递归分裂左子树Update(R); // 更新右子树信息}
}
// 合并操作
int Merge(int L, int R) {if (L == 0 || R == 0) return L + R; // 若有一个子树为空,返回另一个子树push_down(L); // 下传左子树懒标记push_down(R); // 下传右子树懒标记if (t[L].pri > t[R].pri) { // 根据优先级合并t[L].rs = Merge(t[L].rs, R); // 递归合并右子树和RUpdate(L); // 更新左子树信息return L;}else {t[R].ls = Merge(L, t[R].ls); // 递归合并L和左子树Update(R); // 更新右子树信息return R;}
}if (op == 3) { // 翻转区间[x, y]cin >> x >> y;x ^= lastans;y ^= lastans;Split(root[v], y, L, R); // 分裂出前y个数Split(L, x - 1, L, P); // 分裂出区间[x, y]t[P].lazy ^= 1; // 对区间打懒标记root[++version] = Merge(Merge(L, P), R); // 合并并记录新版本}if (op == 4) { // 查询区间和[x, y]cin >> x >> y;x ^= lastans;y ^= lastans;Split(root[v], y, L, R); // 分裂出前y个数Split(L, x - 1, L, P); // 分裂出区间[x, y]lastans = t[P].sum; // 记录区间和cout << lastans << endl;root[++version] = Merge(Merge(L, P), R); // 合并并记录新版本}