平衡树(FHQ treap) 的一些应用

[郁闷的出纳员]([P1486 NOI2004] 郁闷的出纳员 - 洛谷 (luogu.com.cn))

代功能说明:

  1. 数据结构:使用无旋Treap维护员工工资信息

  2. 核心变量

    • delta:累计工资增量(所有员工的工资实际值 = 存储值 + delta)
    • m:工资下界(低于此值的员工会被移除)
    • leave:记录因降薪离开的员工总数
  3. 操作解释

    • 插入(I):仅当工资≥m时才插入(存储基础值 = 实际工资 - delta)
    • 涨薪(A):增加delta值(影响所有员工)
    • 降薪(S)
      • 减少delta值
      • 移除所有基础值 ≤ (m - delta - 1)的员工(即实际工资 < m)
      • 累加离开员工数到leave
    • 查询(F):查询第k高工资(实际值 = 存储值 + delta)
      • 若k大于当前员工数,输出-1
      • 通过查找第(总人数-k+1)小节点实现
  4. 关键技巧

    • 通过维护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;
}

代码功能说明:

  1. 可持久化Treap实现

    • 支持历史版本回溯
    • 所有修改操作均创建新节点,保持历史版本不变
    • 通过复制节点路径实现可持久化
  2. 核心操作

    • 插入(1):将值x插入指定版本树,创建新版本
    • 删除(2):删除值x,若存在则创建新版本
    • 查询排名(3):返回x的排名(小于x的元素数+1)
    • 查询第k小(4):返回第k小的元素值
    • 查询前驱(5):返回小于x的最大元素
    • 查询后继(6):返回大于x的最小元素
  3. 关键函数

    • split():可持久化分裂,复制路径节点
    • merge():可持久化合并,复制路径节点
    • find():查找第k小元素(非递归实现)
    • newnode():创建新节点(支持默认值0)
  4. 特殊处理

    • 前驱/后继不存在时返回±2147483647
    • 删除不存在的元素时保持原树不变
    • 查询操作后恢复完整树结构(操作3/5/6)
  5. 版本管理

    • 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;
}

代码功能说明:

  1. 数据结构:使用带懒标记的Treap实现序列维护

  2. 核心功能:支持区间翻转操作(高效处理序列反转)

  3. 关键操作

    • 初始化:构建初始序列1,2,...,n的Treap
    • 区间翻转
      1. 通过两次split分离出[l, r]区间
      2. 给该区间根节点打翻转标记(tag ^= 1
      3. 重新合并三个区间
    • 标记下传
      • splitmerge前调用pushdown
      • 在输出遍历前调用pushdown
  4. 标记处理机制

    • tag=1表示该子树需要翻转
    • 实际翻转操作延迟到需要访问子树时执行
    • 翻转操作:交换左右子树 + 下传标记
  5. 空间管理

    • 预分配N*30空间(满足操作需求)
    • 每个操作最多创建O(1)新节点(非完全可持久化)
  6. 输出

    • 通过中序遍历输出最终序列
    • 遍历过程中动态下传翻转标记
    • 输出顺序即当前序列顺序

算法特点:

  • 时间复杂度:每次操作O(log n)
  • 空间复杂度:O(n)
  • 高效性:通过懒标记避免立即翻转整个区间
  • 稳定性:依赖Treap的随机优先级保持平衡

该实现专门解决序列区间翻转问题,是处理反转操作的经典数据结构应用。

posted @ 2025-06-02 09:49  ljfyyds  阅读(19)  评论(0)    收藏  举报