平衡树(FHQ treap) 的一些应用
[郁闷的出纳员]([P1486 NOI2004] 郁闷的出纳员 - 洛谷 (luogu.com.cn))
代功能说明:
-
数据结构:使用无旋Treap维护员工工资信息
-
核心变量:
delta:累计工资增量(所有员工的工资实际值 = 存储值 + delta)m:工资下界(低于此值的员工会被移除)leave:记录因降薪离开的员工总数
-
操作解释:
- 插入(I):仅当工资≥m时才插入(存储基础值 = 实际工资 - delta)
- 涨薪(A):增加delta值(影响所有员工)
- 降薪(S):
- 减少delta值
- 移除所有基础值 ≤ (m - delta - 1)的员工(即实际工资 < m)
- 累加离开员工数到
leave
- 查询(F):查询第k高工资(实际值 = 存储值 + delta)
- 若k大于当前员工数,输出-1
- 通过查找第(总人数-k+1)小节点实现
-
关键技巧:
- 通过维护
delta避免频繁更新所有节点 - 降薪时通过Treap分割高效移除低工资员工
- 查询时通过子树大小快速定位第k大元素
- 通过维护
该实现高效处理了动态工资调整和员工淘汰机制,时间复杂度主要操作均为O(log n)。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10; // 定义最大节点数
// Treap节点结构体
struct node
{
int l, r; // 左右子节点索引
int w; // 节点存储的工资值(实际存储的是工资减去累计增量的基础值)
int siz; // 以该节点为根的子树大小
int rd; // 随机优先级,用于维护堆性质
} tr[N];
int delta; // 工资累计增量(所有员工的工资实际值为存储值 + delta)
int cnt; // 节点计数器
int root; // 树根节点索引
int n, m; // n:操作次数, m:工资下界
// 创建新节点
int newnode(int x)
{
tr[++cnt].w = x; // 存储基础工资值
tr[cnt].siz = 1; // 初始化子树大小为1
tr[cnt].rd = rand(); // 生成随机优先级
return cnt; // 返回新节点索引
}
// 更新节点子树大小
void update(int x)
{
tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
// 按值分割Treap:将树p按值k分割成两棵树x和y(x中所有节点值<=k,y中所有节点值>k)
void split(int p, int k, int &x, int &y)
{
if (!p)
{
x = y = 0;
return;
}
if (tr[p].w <= k)
{
x = p;
split(tr[p].r, k, tr[p].r, y); // 递归分割右子树
}
else
{
y = p;
split(tr[p].l, k, x, tr[p].l); // 递归分割左子树
}
update(p); // 更新当前节点信息
}
// 合并两棵Treap(x中所有值 <= y中所有值)
int merge(int x, int y)
{
if (!x || !y)
return x + y;
// 按随机优先级维护堆性质
if (tr[x].rd > tr[y].rd)
{
tr[x].r = merge(tr[x].r, y);
update(x);
return x;
}
else
{
tr[y].l = merge(x, tr[y].l);
update(y);
return y;
}
}
// 查找第k小节点(注意:kth实现的是查找第k小)
int kth(int p, int k)
{
while (1)
{
if (tr[tr[p].l].siz >= k) // 左子树节点数足够
p = tr[p].l;
else
{
if (tr[p].l) // 如果存在左子树
k -= tr[tr[p].l].siz; // 减去左子树节点数
k--; // 减去当前节点
if (!p || k <= 0) // 找到目标节点
return p;
p = tr[p].r; // 在右子树中继续查找
}
}
}
int leave = 0; // 记录因降薪离开的员工总数
int main()
{
srand(99); // 固定随机种子(99)保证结果可重现
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
char opt;
int x, y, k;
cin >> opt >> k;
if (opt == 'I') // 插入操作
{
// 如果工资低于下界m,直接跳过
if (k < m)
continue;
// 存储基础值 = 实际工资 - 当前累计增量delta
k -= delta;
// 按值k分割树
split(root, k, x, y);
// 创建新节点并合并树
root = merge(x, merge(newnode(k), y));
}
else if (opt == 'A') // 全体涨薪
delta += k;
else if (opt == 'S') // 全体降薪
{
delta -= k;
// 分割树:x树包含所有工资低于新下界的员工(基础值 <= m - delta - 1)
split(root, m - delta - 1, x, y);
root = y; // 保留工资达标的员工(y树)
leave += tr[x].siz; // 累加离开员工数
}
else // 查询操作(opt == 'F')
{
// 如果查询的k大于当前员工总数
if (k > tr[root].siz)
cout << -1 << endl;
else
{
// 注意:kth返回第k小,但题目要求第k大
// 所以转化为:第(总人数 - k + 1)小
int pos = kth(root, tr[root].siz - k + 1);
// 输出实际工资 = 存储的基础值 + 当前增量delta
cout << tr[pos].w + delta << endl;
}
}
}
// 输出因降薪离开的总员工数
cout << leave << endl;
return 0;
}
[[模板]可持久化平衡树](P3835 [模板]可持久化平衡树 - 洛谷 (luogu.com.cn))
#include <bits/stdc++.h>
using namespace std;
const int N = 3e7 + 10, INF = 1e9; // 最大节点数和无穷大常量
// Treap节点结构
struct node {
int w; // 节点存储的值
int rd; // 随机优先级
int siz; // 子树大小
int ch[2]; // 左右子节点索引 [0]-左, [1]-右
} tr[N];
int root[N]; // 存储各版本树的根节点
int m, last, ans, t, cnt; // 全局变量(部分未使用)
int n; // 操作总数
// 创建新节点(可选初始化值x)
int newnode(int x = 0) {
tr[++cnt].w = x; // 设置节点值
tr[cnt].siz = 1; // 初始化子树大小
tr[cnt].rd = rand(); // 生成随机优先级
tr[cnt].ch[0] = tr[cnt].ch[1] = 0; // 初始化子节点
return cnt; // 返回新节点索引
}
// 更新节点子树大小
void update(int p) {
tr[p].siz = tr[tr[p].ch[0]].siz + tr[tr[p].ch[1]].siz + 1;
}
// 可持久化分裂:将树p按值k分裂为L(<=k)和R(>k)
void split(int p, int k, int &L, int &R) {
if (!p) {
L = R = 0;
return;
}
// 复制当前节点实现可持久化
if (tr[p].w <= k) {
L = newnode(); // 创建新节点作为左树部分
tr[L] = tr[p]; // 复制原节点数据
// 递归分裂右子树
split(tr[L].ch[1], k, tr[L].ch[1], R);
update(L); // 更新新节点大小
} else {
R = newnode(); // 创建新节点作为右树部分
tr[R] = tr[p]; // 复制原节点数据
// 递归分裂左子树
split(tr[R].ch[0], k, L, tr[R].ch[0]);
update(R); // 更新新节点大小
}
}
// 可持久化合并:合并两棵树x(所有值<=y)和y
int merge(int x, int y) {
if (!x || !y) return x + y;
// 按随机优先级合并(复制节点实现可持久化)
if (tr[x].rd > tr[y].rd) {
int u = newnode(); // 创建新节点
tr[u] = tr[x]; // 复制x节点数据
// 递归合并右子树
tr[u].ch[1] = merge(tr[u].ch[1], y);
update(u); // 更新新节点
return u;
} else {
int u = newnode(); // 创建新节点
tr[u] = tr[y]; // 复制y节点数据
// 递归合并左子树
tr[u].ch[0] = merge(x, tr[u].ch[0]);
update(u); // 更新新节点
return u;
}
}
// 查找第k小的节点
int find(int p, int x) {
while (true) {
if (x <= tr[tr[p].ch[0]].siz) {
p = tr[p].ch[0]; // 在左子树中查找
} else {
// 调整剩余查找位置
if (tr[p].ch[0]) x -= tr[tr[p].ch[0]].siz;
x--; // 减去当前节点
if (x <= 0 || !p) return p; // 找到目标或越界
p = tr[p].ch[1]; // 在右子树中查找
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int tmp, opt, x;
int R1 = 0, R2 = 0, R3 = 0; // 分裂临时变量
scanf("%d%d%d", &tmp, &opt, &x);
// 基于历史版本tmp创建当前版本i
root[i] = root[tmp];
// 操作1:插入元素x
if (opt == 1) {
split(root[i], x, R1, R2); // 分裂为<=x和>x
root[i] = merge(merge(R1, newnode(x)), R2); // 插入新节点后合并
}
// 操作2:删除元素x
if (opt == 2) {
split(root[i], x, R1, R2); // 分裂为<=x和>x
split(R1, x - 1, R1, R3); // 将<=x分裂为<x和=x
// 合并R3的左右子树(删除根节点)
if (R3) {
int newR3 = merge(tr[R3].ch[0], tr[R3].ch[1]);
root[i] = merge(merge(R1, newR3), R2);
} else {
root[i] = merge(R1, R2); // 未找到x时保持原树
}
}
// 操作3:查询x的排名(小于x的元素数量+1)
if (opt == 3) {
split(root[i], x - 1, R1, R2); // 分裂为<x和>=x
cout << tr[R1].siz + 1 << '\n'; // 排名=左子树大小+1
root[i] = merge(R1, R2); // 恢复完整树结构
}
// 操作4:查询第k小元素
if (opt == 4) {
// 直接查找第x小的节点值
cout << tr[find(root[i], x)].w << endl;
// 注意:未创建新版本(root[i]保持原历史版本)
}
// 操作5:查询x的前驱(小于x的最大元素)
if (opt == 5) {
split(root[i], x - 1, R1, R2); // 分裂为<x和>=x
if (R1 == 0) {
cout << "-2147483647" << endl; // 无前驱
} else {
// 在左子树中找最大值(最右节点)
cout << tr[find(R1, tr[R1].siz)].w << endl;
}
root[i] = merge(R1, R2); // 恢复完整树结构
}
// 操作6:查询x的后继(大于x的最小元素)
if (opt == 6) {
split(root[i], x, R1, R2); // 分裂为<=x和>x
if (R2 == 0) {
cout << "2147483647\n"; // 无后继
} else {
// 在右子树中找最小值(最左节点)
cout << tr[find(R2, 1)].w << endl;
}
root[i] = merge(R1, R2); // 恢复完整树结构
}
}
return 0;
}
代码功能说明:
-
可持久化Treap实现:
- 支持历史版本回溯
- 所有修改操作均创建新节点,保持历史版本不变
- 通过复制节点路径实现可持久化
-
核心操作:
- 插入(1):将值x插入指定版本树,创建新版本
- 删除(2):删除值x,若存在则创建新版本
- 查询排名(3):返回x的排名(小于x的元素数+1)
- 查询第k小(4):返回第k小的元素值
- 查询前驱(5):返回小于x的最大元素
- 查询后继(6):返回大于x的最小元素
-
关键函数:
split():可持久化分裂,复制路径节点merge():可持久化合并,复制路径节点find():查找第k小元素(非递归实现)newnode():创建新节点(支持默认值0)
-
特殊处理:
- 前驱/后继不存在时返回±2147483647
- 删除不存在的元素时保持原树不变
- 查询操作后恢复完整树结构(操作3/5/6)
-
版本管理:
root[]数组存储各版本根节点- 每个操作基于历史版本创建新版本
- 查询操作4不改变版本树结构
复杂度分析:
- 时间复杂度:每个操作O(log n)
- 空间复杂度:每次修改创建O(log n)新节点
- 总空间上限:O(n log n)
[P3391 【模板】文艺平衡树](P3391 【模板】文艺平衡树 - 洛谷 (luogu.com.cn))
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
// Treap节点结构体
struct node {
int l, r; // 左右子节点索引
int rd; // 随机优先级(用于维护堆性质)
int w; // 节点存储的值(序列元素值)
int siz; // 子树大小(包含该节点的子树节点总数)
int tag; // 翻转懒标记(0未翻转,1需要翻转)
} tr[N * 30]; // 空间分配:N*30(预留足够空间)
int cnt; // 节点计数器
int n, m; // n:序列长度, m:操作次数
int root; // 当前Treap根节点索引
// 创建新节点
int newnode(int x) {
tr[++cnt].w = x; // 存储值
tr[cnt].rd = rand(); // 随机优先级
tr[cnt].siz = 1; // 初始化子树大小
tr[cnt].tag = 0; // 初始化无翻转标记
return cnt; // 返回新节点索引
}
// 更新节点子树大小
void update(int p) {
tr[p].siz = tr[tr[p].l].siz + tr[tr[p].r].siz + 1;
}
// 下传翻转懒标记
void pushdown(int p) {
if (tr[p].tag) { // 如果有翻转标记
swap(tr[p].l, tr[p].r); // 交换左右子树
// 下传标记给子节点(异或1翻转状态)
tr[tr[p].l].tag ^= 1;
tr[tr[p].r].tag ^= 1;
tr[p].tag = 0; // 清除当前节点标记
}
}
// 按大小分裂Treap:将树p前k个节点分到x,剩余分到y
void split(int p, int k, int &x, int &y) {
if (!p) {
x = y = 0;
return;
}
pushdown(p); // 先下传标记保证左右子树正确
if (tr[tr[p].l].siz + 1 <= k) { // 当前节点属于左部分
x = p;
// 递归分裂右子树(k减去左子树和当前节点大小)
split(tr[p].r, k - tr[tr[p].l].siz - 1, tr[p].r, y);
} else { // 当前节点属于右部分
y = p;
// 递归分裂左子树
split(tr[p].l, k, x, tr[p].l);
}
update(p); // 更新当前节点信息
}
// 合并两棵Treap
int merge(int x, int y) {
if (!x || !y) return x + y;
// 按随机优先级合并(维护堆性质)
if (tr[x].rd > tr[y].rd) {
pushdown(x); // 先下传标记
tr[x].r = merge(tr[x].r, y); // y合并到x的右子树
update(x);
return x;
} else {
pushdown(y); // 先下传标记
tr[y].l = merge(x, tr[y].l); // x合并到y的左子树
update(y);
return y;
}
}
// 中序遍历输出序列(递归实现)
void dfs(int p) {
if (!p) return;
pushdown(p); // 遍历前先下传标记
dfs(tr[p].l); // 遍历左子树
cout << tr[p].w << ' '; // 输出当前节点值
dfs(tr[p].r); // 遍历右子树
}
int main() {
cin >> n >> m;
// 初始化序列:1到n依次插入Treap
for (int i = 1; i <= n; i++) {
root = merge(root, newnode(i));
}
// 处理m次翻转操作
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
int x, y, z;
// 1. 分裂出[1, l-1] -> x
split(root, l - 1, x, y);
// 2. 从y中分裂出[l, r] -> y, 剩余[r+1, n] -> z
split(y, r - l + 1, y, z);
// 3. 对区间[l, r]对应的子树y打翻转标记
tr[y].tag ^= 1;
// 4. 按顺序合并x, y, z
root = merge(merge(x, y), z);
}
// 中序遍历输出最终序列
dfs(root);
return 0;
}
代码功能说明:
-
数据结构:使用带懒标记的Treap实现序列维护
-
核心功能:支持区间翻转操作(高效处理序列反转)
-
关键操作:
- 初始化:构建初始序列
1,2,...,n的Treap - 区间翻转:
- 通过两次
split分离出[l, r]区间 - 给该区间根节点打翻转标记(
tag ^= 1) - 重新合并三个区间
- 通过两次
- 标记下传:
- 在
split和merge前调用pushdown - 在输出遍历前调用
pushdown
- 在
- 初始化:构建初始序列
-
标记处理机制:
tag=1表示该子树需要翻转- 实际翻转操作延迟到需要访问子树时执行
- 翻转操作:交换左右子树 + 下传标记
-
空间管理:
- 预分配
N*30空间(满足操作需求) - 每个操作最多创建O(1)新节点(非完全可持久化)
- 预分配
-
输出:
- 通过中序遍历输出最终序列
- 遍历过程中动态下传翻转标记
- 输出顺序即当前序列顺序
算法特点:
- 时间复杂度:每次操作O(log n)
- 空间复杂度:O(n)
- 高效性:通过懒标记避免立即翻转整个区间
- 稳定性:依赖Treap的随机优先级保持平衡
该实现专门解决序列区间翻转问题,是处理反转操作的经典数据结构应用。

浙公网安备 33010602011771号