数据结构

普通并查集 DSU

// 普通并查集:路径压缩 + 按秩合并,点编号默认是 1..n。
class DSU
{
private:
    vector<int> parent, rank, size;
    int count;
 
public:
    DSU(int n = 0)
    {
        init(n);
    }
 
    void init(int n)
    {
        parent.resize(n + 1);
        rank.assign(n + 1, 0);
        size.assign(n + 1, 1);
        count = n;
        iota(all(parent), 0ll);
    }
 
    int find(int x)
    {
        return parent[x] == x ? x : parent[x] = find(parent[x]);
    }
 
    // 返回是否真的合并了两个原本不同的集合。
    bool merge(int x, int y)
    {
        int rx = find(x), ry = find(y);
        if (rx == ry)
        {
            return false;
        }
 
        if (rank[rx] < rank[ry])
        {
            swap(rx, ry);
        }
        parent[ry] = rx;
        size[rx] += size[ry];
        if (rank[rx] == rank[ry])
        {
            rank[rx]++;
        }
        count--;
        return true;
    }
 
    bool issame(int x, int y)
    {
        return find(x) == find(y);
    }
 
    int getsize(int x)
    {
        return size[find(x)];
    }
 
    int getgroups()
    {
        return count;
    }
};

带权并查集

// ============================================================
// 带权并查集(维护点权之间的差)
// ============================================================
// 默认定义:
//   weight[x] = value[x] - value[parent[x]]
//
// 路径压缩后:
//   weight[x] = value[x] - value[root]
//
// merge(x, y, w) 加入的约束是:
//   value[y] - value[x] = w
//
// diff(x, y) 返回:
//   value[y] - value[x]
//
// 例子:
//   merge(1, 2, 3) 表示 value[2] - value[1] = 3
//   merge(2, 3, 5) 表示 value[3] - value[2] = 5
//   diff(1, 3) 得到 8
//
// merge 的返回值:
//   true  :成功合并,或者原有关系与新约束一致
//   false :x、y 已经连通,但新约束与原关系矛盾
//
// 换题时最容易改错的地方:
//   1. 有的题给的是 value[x] - value[y] = w。
//      此时调用 merge(x, y, -w),不要同时修改多处公式。
//   2. 若维护异或关系,把“+ / -”整体换成 xor;
//      路径压缩和合并公式也必须一起推导,不能只改 diff()。
//   3. 食物链这类模 k 关系:所有 weight 和 w 都需要规范到 [0, k)。
//      相加、相减后也要取模。
//   4. 带权并查集只能维护同一连通块内的相对关系,不能直接得到绝对值。
//
// 合并公式推导(默认把 ry 接到 rx 时):
//   wx = value[x] - value[rx]
//   wy = value[y] - value[ry]
//   又有 value[y] - value[x] = w
//   所以 value[ry] - value[rx] = w + wx - wy
//   即 weight[ry] = w + wx - wy
// ============================================================
 
class WeightedDSU
{
private:
    vector<int> parent, rank, size;
    vector<int> weight;
    int count;
 
public:
    WeightedDSU(int n)
        : parent(n + 1), rank(n + 1), size(n + 1, 1),
          weight(n + 1), count(n)
    {
        iota(all(parent), 0ll);
    }
 
    int find(int x)
    {
        if (parent[x] == x)
        {
            return x;
        }
 
        int old_parent = parent[x];
        parent[x] = find(parent[x]);
        weight[x] += weight[old_parent];
        return parent[x];
    }
 
    bool merge(int x, int y, int w)
    {
        int rx = find(x), ry = find(y);
        int wx = weight[x], wy = weight[y];
 
        if (rx == ry)
        {
            return wy - wx == w;
        }
 
        if (rank[rx] < rank[ry])
        {
            parent[rx] = ry;
            weight[rx] = wy - wx - w;
            size[ry] += size[rx];
        }
        else
        {
            parent[ry] = rx;
            weight[ry] = w + wx - wy;
            size[rx] += size[ry];
            if (rank[rx] == rank[ry])
            {
                rank[rx]++;
            }
        }
 
        count--;
        return true;
    }
 
    bool issame(int x, int y)
    {
        return find(x) == find(y);
    }
 
    // 调用前应保证 issame(x, y) 为 true。
    int diff(int x, int y)
    {
        find(x);
        find(y);
        return weight[y] - weight[x];
    }
 
    bool check(int x, int y, int w)
    {
        return issame(x, y) and diff(x, y) == w;
    }
 
    int getweight(int x)
    {
        find(x);
        return weight[x];
    }
 
    int getsize(int x)
    {
        return size[find(x)];
    }
 
    int getgroups()
    {
        return count;
    }
};

树状数组 BIT

// ============================================================
// 树状数组(全部使用 1 下标)
// ============================================================
// BIT 支持:单点加、单点赋值、前缀和、区间和、前缀 lower_bound。
// RangeBIT 支持:区间加、前缀和、区间和、单点查询。
//
// kth(target) 返回最小的 pos,使 sum(pos) >= target。
// 它要求树状数组维护的是非负频次,否则前缀和不单调,不能二分。
// 若 target > sum(n),返回 n + 1。
// ============================================================
 
class BIT
{
private:
    int n;
    vector<int> c;
 
    int lowbit(int x)
    {
        return x & -x;
    }
 
public:
    BIT(int size = 0) : n(size), c(n + 1)
    {
    }
 
    // arr 使用 1 下标,有效元素为 arr[1..n],arr[0] 空置。
    BIT(const vector<int> &arr) : n(arr.size() - 1), c(n + 1)
    {
        for (int i = 1; i <= n; i++)
        {
            add(i, arr[i]);
        }
    }
 
    void init(int size)
    {
        n = size;
        c.assign(n + 1, 0);
    }
 
    void add(int i, int val)
    {
        while (i <= n)
        {
            c[i] += val;
            i += lowbit(i);
        }
    }
 
    int sum(int i)
    {
        int ans = 0;
        while (i > 0)
        {
            ans += c[i];
            i -= lowbit(i);
        }
        return ans;
    }
 
    int query(int l, int r)
    {
        if (l > r)
        {
            return 0;
        }
        return sum(r) - sum(l - 1);
    }
 
    int get(int i)
    {
        return query(i, i);
    }
 
    void update(int i, int val)
    {
        int delta = val - get(i);
        add(i, delta);
    }
 
    int kth(int target)
    {
        if (target <= 0)
        {
            return 1;
        }
 
        int pos = 0;
        int pre = 0;
        int step = 1;
        while ((step << 1) <= n)
        {
            step <<= 1;
        }
 
        for (; step; step >>= 1)
        {
            int nxt = pos + step;
            if (nxt <= n and pre + c[nxt] < target)
            {
                pos = nxt;
                pre += c[nxt];
            }
        }
        return pos + 1;
    }
 
    void clear()
    {
        fill(all(c), 0);
    }
 
    int size()
    {
        return n;
    }
};
 
class RangeBIT
{
private:
    int n;
    BIT c1, c2;
 
    void add(BIT &c, int pos, int val)
    {
        if (pos <= n)
        {
            c.add(pos, val);
        }
    }
 
public:
    RangeBIT(int n = 0) : n(n), c1(n), c2(n)
    {
    }
 
    // 在 [l, r] 全部增加 val。
    void add(int l, int r, int val)
    {
        add(c1, l, val);
        add(c1, r + 1, -val);
        add(c2, l, val * (l - 1));
        add(c2, r + 1, -val * r);
    }
 
    int sum(int pos)
    {
        return c1.sum(pos) * pos - c2.sum(pos);
    }
 
    int query(int l, int r)
    {
        if (l > r)
        {
            return 0;
        }
        return sum(r) - sum(l - 1);
    }
 
    int get(int pos)
    {
        return query(pos, pos);
    }
};

线段树 SegTree

// ============================================================
// 通用线段树(数组使用 1 下标)
// ============================================================
// 当前支持:
//   1. 区间加:add(l, r, val)
//   2. 区间赋值:change(l, r, val)
//   3. 区间取 min:setmin(l, r, val),即 a[i] = min(a[i], val)
//   4. 区间取 max:setmax(l, r, val),即 a[i] = max(a[i], val)
//   5. 区间和:getsum(l, r)
//   6. 区间最小值:getmin(l, r)
//   7. 区间最大值:getmax(l, r)
//   8. 单点赋值:modify(pos, val)
//   9. 单点查询:get(pos)
//  10. 在区间内寻找第一个/最后一个值 >= val 的位置
//
// 默认语义:
//   change 是“覆盖”,add 是“在当前值上增加”。
//   若同一节点先 change 再 add,下传时也必须先下传 change,再下传 add。
//   这是 down() 中两个懒标记顺序不能交换的原因。
//
// 换题时常见改动:
//   1. 只需要 add + getsum:可以删除 mmin、mmax、ch、has_ch 相关代码。
//   2. 只需要 add + getmax/getmin:可以删除 sum;lazy_add 不再需要 len。
//   3. 只需要 change:可以删除 ad 和 lazy_add。
//   4. 求区间乘积、gcd 等:重点修改 up() 和查询时的合并方式;
//      但区间 add 通常不能直接维护 gcd/乘积,不能只改变量名。
//   5. 找第一个满足条件的位置:find_first_ge 依赖“区间最大值”。
//      如果改成寻找 <= val,应改用 mmin 剪枝。
//
// 注意:
//   - 构造函数 SegTree(vector<int> &v) 要求 v 的有效元素是 v[1..n]。
//   - n 必须至少为 1。
//   - 若题目中的值和区间和可能超过 long long,需要改成 __int128。
// ============================================================
 
class SegTree
{
private:
    int n;
    // sum[i]、mmin[i]、mmax[i] 分别记录节点 i 所管区间的和、最小值、最大值。
    vector<int> sum, mmin, mmax;
    // smn/smx:严格次小值/严格次大值;cmn/cmx:最小值/最大值出现次数。
    // 这些信息用于 Segment Tree Beats,在不下沉到叶子的情况下完成区间 chmin/chmax。
    vector<int> smn, smx, cmn, cmx;
    // ad[i]:节点 i 所管区间整体还要增加多少,尚未下传给儿子。
    // ch[i]:节点 i 所管区间整体要被赋成什么值。
    // has_ch[i]:不能用 ch[i] 是否为 0 判断赋值标记,因为“赋值为 0”也是合法操作。
    vector<int> ad, ch;
    vector<int> has_ch;
    // 保存建树时使用的原数组,有效范围是 v[1..n]。
    vector<int> v;
 
    // 把“整个节点 i 所管区间增加 val”直接作用到节点信息上。
    // len 是该节点所管区间长度:区间和增加 val * len,最值只增加 val。
    // 这里只更新当前节点并累计懒标记,不立刻递归修改它的儿子。
    void lazy_add(int i, int val, int len)
    {
        sum[i] += val * len;
        mmin[i] += val;
        mmax[i] += val;
        if (smn[i] != LLONG_MAX)
            smn[i] += val;
        if (smx[i] != LLONG_MIN)
            smx[i] += val;
        // 已有赋值标记时直接合并到赋值值,保持 has_ch 与 ad 不同时存在。
        // 这能让后续 setmin/setmax 与 change/add 的组合保持唯一语义。
        if (has_ch[i])
            ch[i] += val;
        else
            ad[i] += val;
    }
 
    // 把“整个节点 i 所管区间赋值为 val”直接作用到节点信息上。
    // 赋值会覆盖此前尚未下传的加法,所以必须把 ad[i] 清零。
    // has_ch[i] 设为 1,表示以后 down() 时需要把赋值继续传给两个儿子。
    void lazy_change(int i, int val, int len)
    {
        sum[i] = val * len;
        mmin[i] = mmax[i] = val;
        smn[i] = LLONG_MAX;
        smx[i] = LLONG_MIN;
        cmn[i] = cmx[i] = len;
        ch[i] = val;
        ad[i] = 0;
        has_ch[i] = 1;
    }
 
    // 前提:smx[i] < val < mmax[i]。只有当前最大值会被压低到 val。
    void lazy_setmin(int i, int val)
    {
        if (mmax[i] <= val)
            return;
        sum[i] += (val - mmax[i]) * cmx[i];
        if (mmin[i] == mmax[i])
            mmin[i] = val;
        else if (smn[i] == mmax[i])
            smn[i] = val;
        mmax[i] = val;
        if (has_ch[i])
            ch[i] = val;
    }
 
    // 前提:mmin[i] < val < smn[i]。只有当前最小值会被抬高到 val。
    void lazy_setmax(int i, int val)
    {
        if (mmin[i] >= val)
            return;
        sum[i] += (val - mmin[i]) * cmn[i];
        if (mmax[i] == mmin[i])
            mmax[i] = val;
        else if (smx[i] == mmin[i])
            smx[i] = val;
        mmin[i] = val;
        if (has_ch[i])
            ch[i] = val;
    }
 
    // 用两个儿子的信息重新计算父节点 i。
    // 每次对子区间递归修改完成后,都要调用 up(i)。
    void up(int i)
    {
        sum[i] = sum[i << 1] + sum[i << 1 | 1];
 
        if (mmax[i << 1] == mmax[i << 1 | 1])
        {
            mmax[i] = mmax[i << 1];
            cmx[i] = cmx[i << 1] + cmx[i << 1 | 1];
            smx[i] = max(smx[i << 1], smx[i << 1 | 1]);
        }
        else if (mmax[i << 1] > mmax[i << 1 | 1])
        {
            mmax[i] = mmax[i << 1];
            cmx[i] = cmx[i << 1];
            smx[i] = max(smx[i << 1], mmax[i << 1 | 1]);
        }
        else
        {
            mmax[i] = mmax[i << 1 | 1];
            cmx[i] = cmx[i << 1 | 1];
            smx[i] = max(mmax[i << 1], smx[i << 1 | 1]);
        }
 
        if (mmin[i << 1] == mmin[i << 1 | 1])
        {
            mmin[i] = mmin[i << 1];
            cmn[i] = cmn[i << 1] + cmn[i << 1 | 1];
            smn[i] = min(smn[i << 1], smn[i << 1 | 1]);
        }
        else if (mmin[i << 1] < mmin[i << 1 | 1])
        {
            mmin[i] = mmin[i << 1];
            cmn[i] = cmn[i << 1];
            smn[i] = min(smn[i << 1], mmin[i << 1 | 1]);
        }
        else
        {
            mmin[i] = mmin[i << 1 | 1];
            cmn[i] = cmn[i << 1 | 1];
            smn[i] = min(mmin[i << 1], smn[i << 1 | 1]);
        }
    }
 
    // 把节点 i 上尚未下传的懒标记传给左右儿子。
    // ln、rn 分别是左儿子、右儿子所管区间的长度。
    // 必须先传 change 再传 add,因为赋值会覆盖旧值,加法是在赋值后的新值上继续增加。
    void down(int i, int ln, int rn)
    {
        if (has_ch[i])
        {
            lazy_change(i << 1, ch[i], ln);
            lazy_change(i << 1 | 1, ch[i], rn);
            has_ch[i] = 0;
        }
 
        if (ad[i])
        {
            lazy_add(i << 1, ad[i], ln);
            lazy_add(i << 1 | 1, ad[i], rn);
            ad[i] = 0;
        }
 
        // 父节点可能做过 chmin/chmax;把父节点的新上下界同步给儿子。
        if (mmax[i << 1] > mmax[i])
            lazy_setmin(i << 1, mmax[i]);
        if (mmax[i << 1 | 1] > mmax[i])
            lazy_setmin(i << 1 | 1, mmax[i]);
        if (mmin[i << 1] < mmin[i])
            lazy_setmax(i << 1, mmin[i]);
        if (mmin[i << 1 | 1] < mmin[i])
            lazy_setmax(i << 1 | 1, mmin[i]);
    }
 
    // 建立当前节点 i,它负责原数组区间 [l, r]。
    // 到达叶子时,直接读取 v[l];否则递归建立两个儿子后调用 up(i)。
    void build(int l, int r, int i)
    {
        ad[i] = ch[i] = has_ch[i] = 0;
        if (l == r)
        {
            sum[i] = mmin[i] = mmax[i] = v[l];
            smn[i] = LLONG_MAX;
            smx[i] = LLONG_MIN;
            cmn[i] = cmx[i] = 1;
            return;
        }
 
        int mid = (l + r) >> 1;
        build(l, mid, i << 1);
        build(mid + 1, r, i << 1 | 1);
        up(i);
    }
 
    // 递归执行区间加。
    // jobl、jobr:本次真正想修改的目标区间。
    // l、r      :当前节点 i 所管的区间。
    // i         :当前线段树节点编号,左儿子 i<<1,右儿子 i<<1|1。
    void add(int jobl, int jobr, int val, int l, int r, int i)
    {
        if (jobl <= l and r <= jobr)
        {
            lazy_add(i, val, r - l + 1);
            return;
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        if (jobl <= mid)
        {
            add(jobl, jobr, val, l, mid, i << 1);
        }
        if (jobr > mid)
        {
            add(jobl, jobr, val, mid + 1, r, i << 1 | 1);
        }
        up(i);
    }
 
    // 递归执行区间赋值,参数含义与上面的 add 相同。
    // 如果当前区间被目标区间完整覆盖,就打 change 懒标记,不再继续向下递归。
    void change(int jobl, int jobr, int val, int l, int r, int i)
    {
        if (jobl <= l and r <= jobr)
        {
            lazy_change(i, val, r - l + 1);
            return;
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        if (jobl <= mid)
        {
            change(jobl, jobr, val, l, mid, i << 1);
        }
        if (jobr > mid)
        {
            change(jobl, jobr, val, mid + 1, r, i << 1 | 1);
        }
        up(i);
    }
 
    void setmin(int jobl, int jobr, int val, int l, int r, int i)
    {
        if (mmax[i] <= val)
            return;
        if (jobl <= l and r <= jobr and smx[i] < val)
        {
            lazy_setmin(i, val);
            return;
        }
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        if (jobl <= mid)
            setmin(jobl, jobr, val, l, mid, i << 1);
        if (jobr > mid)
            setmin(jobl, jobr, val, mid + 1, r, i << 1 | 1);
        up(i);
    }
 
    void setmax(int jobl, int jobr, int val, int l, int r, int i)
    {
        if (mmin[i] >= val)
            return;
        if (jobl <= l and r <= jobr and smn[i] > val)
        {
            lazy_setmax(i, val);
            return;
        }
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        if (jobl <= mid)
            setmax(jobl, jobr, val, l, mid, i << 1);
        if (jobr > mid)
            setmax(jobl, jobr, val, mid + 1, r, i << 1 | 1);
        up(i);
    }
 
    // 递归查询目标区间 [jobl, jobr] 的区间和。
    // 查询前调用 down(),保证儿子的信息包含父节点此前积累的懒标记。
    int getsum(int jobl, int jobr, int l, int r, int i)
    {
        if (jobl <= l and r <= jobr)
        {
            return sum[i];
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        int ans = 0;
        if (jobl <= mid)
        {
            ans += getsum(jobl, jobr, l, mid, i << 1);
        }
        if (jobr > mid)
        {
            ans += getsum(jobl, jobr, mid + 1, r, i << 1 | 1);
        }
        return ans;
    }
 
    // 递归查询目标区间 [jobl, jobr] 的最小值。
    // ans 初始为正无穷,因为 min(正无穷, 合法答案) 仍是合法答案。
    int getmin(int jobl, int jobr, int l, int r, int i)
    {
        if (jobl <= l and r <= jobr)
        {
            return mmin[i];
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        int ans = LLONG_MAX;
        if (jobl <= mid)
        {
            ans = min(ans, getmin(jobl, jobr, l, mid, i << 1));
        }
        if (jobr > mid)
        {
            ans = min(ans, getmin(jobl, jobr, mid + 1, r, i << 1 | 1));
        }
        return ans;
    }
 
    // 递归查询目标区间 [jobl, jobr] 的最大值。
    // ans 初始为负无穷,因为 max(负无穷, 合法答案) 仍是合法答案。
    int getmax(int jobl, int jobr, int l, int r, int i)
    {
        if (jobl <= l and r <= jobr)
        {
            return mmax[i];
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        int ans = LLONG_MIN;
        if (jobl <= mid)
        {
            ans = max(ans, getmax(jobl, jobr, l, mid, i << 1));
        }
        if (jobr > mid)
        {
            ans = max(ans, getmax(jobl, jobr, mid + 1, r, i << 1 | 1));
        }
        return ans;
    }
 
    // 在 [jobl, jobr] 中寻找最靠左且数组值 >= val 的位置,不存在返回 -1。
    // 如果当前区间与目标区间无交集,或者当前区间最大值都小于 val,可以直接剪枝。
    // 先递归左儿子,只有左边找不到时才找右边,因此得到的是“第一个位置”。
    int find_first_ge(int jobl, int jobr, int val, int l, int r, int i)
    {
        if (r < jobl or jobr < l or mmax[i] < val)
        {
            return -1;
        }
        if (l == r)
        {
            return l;
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        int ans = find_first_ge(jobl, jobr, val, l, mid, i << 1);
        if (ans == -1)
        {
            ans = find_first_ge(jobl, jobr, val, mid + 1, r, i << 1 | 1);
        }
        return ans;
    }
 
    // 在 [jobl, jobr] 中寻找最靠右且数组值 >= val 的位置,不存在返回 -1。
    // 与 find_first_ge 相同,只是优先递归右儿子,因此得到的是“最后一个位置”。
    int find_last_ge(int jobl, int jobr, int val, int l, int r, int i)
    {
        if (r < jobl or jobr < l or mmax[i] < val)
        {
            return -1;
        }
        if (l == r)
        {
            return l;
        }
 
        int mid = (l + r) >> 1;
        down(i, mid - l + 1, r - mid);
        int ans = find_last_ge(jobl, jobr, val, mid + 1, r, i << 1 | 1);
        if (ans == -1)
        {
            ans = find_last_ge(jobl, jobr, val, l, mid, i << 1);
        }
        return ans;
    }
 
public:
    // 建立一个长度为 n、初始值全部为 0 的线段树。
    SegTree(int n) : n(n), sum(n << 2), mmin(n << 2), mmax(n << 2),
                     smn(n << 2), smx(n << 2), cmn(n << 2), cmx(n << 2),
                     ad(n << 2), ch(n << 2), has_ch(n << 2), v(n + 1)
    {
        build(1, n, 1);
    }
 
    // 根据 1 下标数组 v 建树:v[0] 空置,有效元素为 v[1..n]。
    SegTree(vector<int> &v) : n(v.size() - 1), sum(n << 2), mmin(n << 2),
                              mmax(n << 2), smn(n << 2), smx(n << 2),
                              cmn(n << 2), cmx(n << 2), ad(n << 2), ch(n << 2),
                              has_ch(n << 2), v(v)
    {
        build(1, n, 1);
    }
 
    // 对闭区间 [l, r] 的每个数增加 val。
    void add(int l, int r, int val)
    {
        add(l, r, val, 1, n, 1);
    }
 
    // 把闭区间 [l, r] 的每个数全部赋值为 val。
    void change(int l, int r, int val)
    {
        if (l > r)
            return;
        change(l, r, val, 1, n, 1);
    }
 
    // 对闭区间 [l, r] 执行 a[i] = min(a[i], val)。
    void setmin(int l, int r, int val)
    {
        if (l > r)
            return;
        setmin(l, r, val, 1, n, 1);
    }
 
    // 对闭区间 [l, r] 执行 a[i] = max(a[i], val)。
    void setmax(int l, int r, int val)
    {
        if (l > r)
            return;
        setmax(l, r, val, 1, n, 1);
    }
 
    // 单点赋值:把下标 pos 的值改成 val。
    void modify(int pos, int val)
    {
        change(pos, pos, val);
    }
 
    // 返回闭区间 [l, r] 的元素和。
    int getsum(int l, int r)
    {
        return getsum(l, r, 1, n, 1);
    }
 
    // 返回闭区间 [l, r] 的最小值。
    int getmin(int l, int r)
    {
        return getmin(l, r, 1, n, 1);
    }
 
    // 返回闭区间 [l, r] 的最大值。
    int getmax(int l, int r)
    {
        return getmax(l, r, 1, n, 1);
    }
 
    // 返回下标 pos 当前的值。
    int get(int pos)
    {
        return getsum(pos, pos);
    }
 
    // 返回 [l, r] 内第一个值 >= val 的下标,不存在返回 -1。
    int find_first_ge(int l, int r, int val)
    {
        return find_first_ge(l, r, val, 1, n, 1);
    }
 
    // 返回 [l, r] 内最后一个值 >= val 的下标,不存在返回 -1。
    int find_last_ge(int l, int r, int val)
    {
        return find_last_ge(l, r, val, 1, n, 1);
    }
 
    // 兼容旧模板:update 表示区间加,query 表示区间和。
    void update(int l, int r, int val)
    {
        add(l, r, val);
    }
 
    int query(int l, int r)
    {
        return getsum(l, r);
    }
 
    // 返回当前维护的数组长度。
    int size()
    {
        return n;
    }
};

左偏树与可并堆

// ============================================================
// 左偏树(可并堆 / Mergeable Heap,节点编号从 1 开始)
// ============================================================
// 支持:
//   1. newnode(val)             新建一个只有一个节点的堆,返回节点编号
//   2. merge(x, y)              合并两个堆,返回新根
//   3. push(root, val)          插入元素,返回新根
//   4. top(root)                查询堆顶值
//   5. top_id(root)             查询堆顶节点编号
//   6. pop(root)                删除堆顶,返回新根
//   7. add_all(root, val)       整个堆的所有值增加 val
//   8. size(root) / empty(root) 查询大小 / 判空
//
// 默认是小根堆:
//   LeftistTree<int> heap;
// 改成大根堆:
//   LeftistTree<int, greater<int>> heap;
//
// 左偏树性质:
//   dis[x] 表示 x 到最近空儿子的距离,规定 dis[0] = 0。
//   始终保持 dis[ls[x]] >= dis[rs[x]],因此右链长度为 O(log n)。
//   merge 是所有操作的核心,单次合并 / 插入 / 删除堆顶均为 O(log n)。
//
// 重要约束:
//   - merge(x, y) 要求 x、y 是两个互不相交的堆,否则会形成环。
//   - pop(root) 后旧根已经删除,不要继续把旧根当成合法堆使用。
//   - add_all 是“整堆统一加”,不会改变堆内相对大小,因此可以懒标记。
//   - 若只需要最基础左偏树,可删除 lazy、apply、down、add_all。
//   - 值相同时按节点编号小的优先,方便题目要求稳定判定。
// ============================================================
 
template <class T = int, class Compare = less<T>>
class LeftistTree
{
private:
    vector<T> val, lazy;
    vector<int> ls, rs, dis, siz;
    vector<char> deleted;
    Compare cmp;
 
    // a 是否应该排在 b 前面。
    // 小根堆时值小的优先,大根堆时值大的优先;值相同则编号小的优先。
    bool better(int a, int b) const
    {
        if (cmp(val[a], val[b]))
        {
            return true;
        }
        if (cmp(val[b], val[a]))
        {
            return false;
        }
        return a < b;
    }
 
    void apply(int x, const T &v)
    {
        if (!x)
        {
            return;
        }
        val[x] += v;
        lazy[x] += v;
    }
 
    void down(int x)
    {
        if (!x or lazy[x] == T{})
        {
            return;
        }
        apply(ls[x], lazy[x]);
        apply(rs[x], lazy[x]);
        lazy[x] = T{};
    }
 
    void up(int x)
    {
        if (dis[ls[x]] < dis[rs[x]])
        {
            swap(ls[x], rs[x]);
        }
        dis[x] = dis[rs[x]] + 1;
        siz[x] = siz[ls[x]] + siz[rs[x]] + 1;
    }
 
public:
    LeftistTree(int reserve_n = 0)
    {
        val.reserve(reserve_n + 1);
        lazy.reserve(reserve_n + 1);
        ls.reserve(reserve_n + 1);
        rs.reserve(reserve_n + 1);
        dis.reserve(reserve_n + 1);
        siz.reserve(reserve_n + 1);
        deleted.reserve(reserve_n + 1);
 
        // 0 号节点表示空节点。
        val.push_back(T{});
        lazy.push_back(T{});
        ls.push_back(0);
        rs.push_back(0);
        dis.push_back(0);
        siz.push_back(0);
        deleted.push_back(1);
    }
 
    // 新建单点堆并返回节点编号。
    int newnode(const T &v)
    {
        int id = val.size();
        val.push_back(v);
        lazy.push_back(T{});
        ls.push_back(0);
        rs.push_back(0);
        dis.push_back(1);
        siz.push_back(1);
        deleted.push_back(0);
        return id;
    }
 
    // 合并两个互不相交的堆,返回新根。
    int merge(int x, int y)
    {
        if (!x or !y)
        {
            return x | y;
        }
        if (!better(x, y))
        {
            swap(x, y);
        }
 
        down(x);
        rs[x] = merge(rs[x], y);
        up(x);
        return x;
    }
 
    // 向 root 所在堆插入一个值,返回新根。
    int push(int root, const T &v)
    {
        return merge(root, newnode(v));
    }
 
    // 查询堆顶值;调用者须保证 root != 0。
    const T &top(int root) const
    {
        return val[root];
    }
 
    int top_id(int root) const
    {
        return root;
    }
 
    // 删除堆顶并返回新根。
    int pop(int root)
    {
        down(root);
        int x = ls[root], y = rs[root];
        ls[root] = rs[root] = 0;
        dis[root] = siz[root] = 0;
        deleted[root] = 1;
        return merge(x, y);
    }
 
    // 整个堆统一增加 v,O(1)。
    void add_all(int root, const T &v)
    {
        apply(root, v);
    }
 
    int size(int root) const
    {
        return siz[root];
    }
 
    bool empty(int root) const
    {
        return root == 0;
    }
 
    bool alive(int id) const
    {
        return id > 0 and id < (int)deleted.size() and !deleted[id];
    }
 
    int nodes() const
    {
        return (int)val.size() - 1;
    }
};
 
// ============================================================
// 常见用法 1:维护若干个“根编号”
// ============================================================
/*
LeftistTree<int> heap(n);
vector<int> root(k + 1);
 
root[1] = heap.push(root[1], 10);
root[1] = heap.push(root[1], 3);
root[2] = heap.push(root[2], 7);
 
root[1] = heap.merge(root[1], root[2]);
root[2] = 0; // 两个堆已经合并,原来的 root[2] 必须作废。
 
cout << heap.top(root[1]) << endl; // 3
root[1] = heap.pop(root[1]);
cout << heap.top(root[1]) << endl; // 7
*/
 
// ============================================================
// 常见用法 2:每个初始节点属于一个可合并堆(P3377 类型)
// ============================================================
// 这种题通常有两种操作:
//   merge_set(x, y):合并 x、y 当前所属的堆;
//   pop_set(x)     :删除 x 当前所属堆的堆顶,并返回 {堆顶编号, 堆顶值}。
//
// 注意:
//   删除堆顶后,并查集代表元需要改成新的堆根。
//   已删除节点再次参与操作时,本封装直接忽略。
//   该封装不提供 add_all;如果题目同时需要整堆加,根映射需按题意额外维护。
// ============================================================
 
template <class T = int, class Compare = less<T>>
class MergeableHeap
{
private:
    LeftistTree<T, Compare> heap;
    vector<int> fa;
    vector<char> removed;
 
    int find(int x)
    {
        return fa[x] == x ? x : fa[x] = find(fa[x]);
    }
 
public:
    // a 必须是 1 下标数组,有效范围 a[1..n]。
    MergeableHeap(const vector<T> &a) : heap((int)a.size() - 1)
    {
        int n = (int)a.size() - 1;
        fa.resize(n + 1);
        removed.assign(n + 1, 0);
        fa[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            int id = heap.newnode(a[i]);
            fa[i] = id;
        }
    }
 
    bool alive(int x) const
    {
        return x > 0 and x < (int)removed.size() and !removed[x];
    }
 
    // 合并 x、y 所属的堆;任一点已删除时忽略。
    void merge_set(int x, int y)
    {
        if (!alive(x) or !alive(y))
        {
            return;
        }
        int rx = find(x), ry = find(y);
        if (rx == ry)
        {
            return;
        }
        int root = heap.merge(rx, ry);
        fa[rx] = fa[ry] = fa[root] = root;
    }
 
    // 返回 x 所属堆的堆顶编号;x 已删除时返回 -1。
    int top_id(int x)
    {
        return alive(x) ? find(x) : -1;
    }
 
    // 返回 x 所属堆的堆顶值;调用者须保证 x 未删除。
    const T &top(int x)
    {
        return heap.top(find(x));
    }
 
    // 删除 x 所属堆的堆顶,返回 {被删编号, 被删值}。
    // x 已删除时返回 {-1, T{}}。
    pair<int, T> pop_set(int x)
    {
        if (!alive(x))
        {
            return {-1, T{}};
        }
 
        int oldroot = find(x);
        T answer = heap.top(oldroot);
        int newroot = heap.pop(oldroot);
        removed[oldroot] = 1;
        fa[oldroot] = newroot;
        if (newroot)
        {
            fa[newroot] = newroot;
        }
        return {oldroot, answer};
    }
};
 
// ============================================================
// P3377 类型 solve 示例
// ============================================================
/*
void solve()
{
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
 
    MergeableHeap<int> heap(a);
    while (m--)
    {
        int op, x, y;
        cin >> op >> x;
        if (op == 1)
        {
            cin >> y;
            heap.merge_set(x, y);
        }
        else
        {
            auto [id, value] = heap.pop_set(x);
            if (id == -1)
            {
                cout << -1 << endl;
            }
            else
            {
                cout << value << endl;
            }
        }
    }
}
*/