「BZOJ3065」带插入区间第K小值 替罪羊树×线段树

题目描述

从前有\(n\)只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力\(a_i\)。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间\(k\)小值。他每次向它的随从伏特提出这样的问题: 从左往右第\(x\)个到第\(y\)个跳蚤中,a[i]第\(k\)小的值是多少。

这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王
的询问。

这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。

这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。

这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间\(k\)小值在线查询。

输入

第一行一个正整数\(n\),表示原来有\(n\)只跳蚤排成一行做早操。
第二行有\(n\)个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数\(q\),表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共\(m\)只跳蚤)

  1. \(Q\ x\ y\ k\): 询问从左至右第\(x\)只跳蚤到从左至右第\(y\)只跳蚤中,弹跳力第\(k\)小的跳蚤的弹跳力是多少。
    \((1 ≤ x ≤ y ≤ m, 1 ≤ k ≤ y - x + 1)\)
  2. \(M\ x\ val\): 将从左至右第x只跳蚤的弹跳力改为\(val\)
    \((1 <= x <= m)\)
  3. \(I\ x\ val\): 在从左至右第x只跳蚤的前面插入一只弹跳力为\(val\)的跳蚤。即插入后从左至右第\(x\)只跳蚤是刚插入的跳蚤。
    \((1 <= x <= m + 1)\)

为了体现在线操作,设\(lastans\)为上一次查询的时候程序输出的结果,如果之前没有查询过,则\(lastans = 0\)\(Q、M、I\)操作的数全都要异或\(lastans\)才是真正输入。

输出

对于每个询问输出回答,每行一个回答。

样例

样例输入

10
10 5 8 28 0 19 2 31 1 22 
30
I 6 9
M 1 11
I 8 17
M 1 31
M 6 26
Q 2 7 6
I 23 30
M 31 7
I 22 27
M 26 18
Q 26 17 31
I 5 2
I 18 13
Q 3 3 3
I 27 19
Q 23 23 30
Q 5 13 5
I 3 0
M 15 27
Q 0 28 13
Q 3 29 11
M 2 8
Q 12 5 7
I 30 19
M 11 19
Q 17 8 29
M 29 4
Q 3 0 12
I 7 18
M 29 27

样例输出

28
2
31
0
14
15
14
27
15
14

数据范围

\(n ≤ 35000\)
插入个数\(≤ 35000\),修改个数$ ≤ 70000\(,查询个数\) ≤ 70000$。
$0 ≤ \(任意时刻的权值\)≤ 70000$。

题解

神题给跪了……
怕不是可以当成一个模板背……

不过如果不算低估替罪羊套线段树的巨量内存消耗而造成的\(RE\),可以算是\(1A\)……还是切入正题吧,不要东扯西扯了

如果我们无视插入这个操作,就是一道傻逼的树状数组套主席树模板了。所以我们首先要解决插入的问题。

如果可以离线,我们可以把所有会插入元素的位置先空出来(可以假装存在,赋一个\(inf\),保证不会被查到,然后把插入变成修改即可),做区间第\(k\)小就没有问题了。
然而偏偏这道题要强制在线,我们就必须想办法动态维护线段树序列。鉴于可能会在任意一个位置插入,我们自然想到了平衡树维护。

那么一个比较简陋的思路就出现了,我们用一棵平衡树维护序列,负责插入,然后套一个线段树,负责修改和询问。不过这个思路还很不完善——查询的时候怎么查?发现可以类似于树状数组套主席树的方法,平衡树的每个节点不只保存这个节点的信息,而是整棵子树的信息。

对于每个查询,我们在平衡树上找到对应的节点(这些节点的平衡树信息合并后就是询问的区间,显然有些节点需要加上,有些需要减掉,所以这些点是加权的\((1\ or\ -1)\)),进行线段树上的二分答案,就可以查询出答案。

但是我们会发现,平衡树没有那么容易套其他的树,因为平衡树的结构是会改变的,一旦旋转起来\((AVL、SBT、Splay)\)或是合并和拆分\((Treap)\),线段树的形态也会发生改变,所以我们需要一种更稳定的结构——替罪羊树(都是套路)。

然后就使劲儿写吧……反正\(200+\)行是稳了……

\(PS:\)注意一下内存的问题,线段树必须写成动态开点,并且内存要记得回收,不然\(RE\)\(MLE\)稳稳的。还有,标题里的\(\times\)是套的意思,与\(+\)不一样。

\(Code:\)

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define N 200005
#define M 20000005
#define seg 0, 70000
#define lim 0.77
stackpool;
int n, m;
struct SGT
{
    int lc, rc, sz;
}S[M];
struct SCT
{
    int root, cnt;
    int lc[N], rc[N];
    int rt[N], dt[N], sz[N];
    int arr[N], num;
    int A[2][N << 2], ct[2];
    //线段树部分
    int Newnode()//新建节点
    {
        int x = pool.top();
        pool.pop();
        return x;
    }
    void Recycle(int &x)//回收空间
    {
        pool.push(x);
        S[x].lc = S[x].rc = S[x].sz = 0;
        x = 0;
    }
    void Insert(int &q, int l, int r, int a, int v)//插入元素
    {
        if (!q)
            q = Newnode();
        S[q].sz += v;
        if (l == r)
            return;
        int mid = (l + r) >> 1;
        if (a <= mid)
            Insert(S[q].lc, l, mid, a, v);
        else
            Insert(S[q].rc, mid + 1, r, a, v);
    }
    void Delete(int &q)//回收树
    {
        if (!q)
            return;
        Delete(S[q].lc);
        Delete(S[q].rc);
        Recycle(q);
    }
    //替罪羊树部分
    void Pushup(int q)
    {
        sz[q] = sz[lc[q]] + sz[rc[q]] + 1;
    }
    void Getarray(int q)//拍扁
    {
        if (lc[q])
            Getarray(lc[q]);
        arr[++num] = q;
        if (rc[q])
            Getarray(rc[q]);
    }
    void Rebuild(int &q, int l, int r)//重建
    {
        if (l > r)
        {
            q = 0;
            return;
        }
        int mid = (l + r) >> 1;
        q = arr[mid];
        Delete(rt[q]);
        for (int i = l; i <= r; i++)
            Insert(rt[q], seg, dt[arr[i]], 1);
        Rebuild(lc[q], l, mid - 1);
        Rebuild(rc[q], mid + 1, r);
        Pushup(q);
    }
    void Maintain(int &q)//整个拍扁重建环节
    {
        num = 0;
        Getarray(q);
        Rebuild(q, 1, num);
    }
    void Insert(int &q, int k, int a)//插入在第k个元素前
    {
        if (!q)
        {
            q = ++cnt;
            dt[q] = a;
            sz[q] = 1;
            Insert(rt[q], seg, a, 1);
            return;
        }
        Insert(rt[q], seg, a, 1);
        if (k <= sz[lc[q]] + 1)
            Insert(lc[q], k, a);
        else
            Insert(rc[q], k - sz[lc[q]] - 1, a);
        Pushup(q);
    }
    void Check(int &q, int k)//从上往下检查重建
    {
        if (sz[q] * lim < sz[lc[q]] || sz[q] * lim < sz[rc[q]])
        {
            Maintain(q);
            return;
        }
        if (k == sz[lc[q]] + 1)
            return;
        if (k <= sz[lc[q]] + 1)
            Check(lc[q], k);
        else
            Check(rc[q], k - sz[lc[q]] - 1);
    }
    int Modify(int &q, int k, int a)//修改第k个元素
    {
        if (k == sz[lc[q]] + 1)
        {
            int b = dt[q];
            Insert(rt[q], seg, a, 1);
            Insert(rt[q], seg, b, -1);
            dt[q] = a;
            return b;
        }
        int b;
        if (k <= sz[lc[q]] + 1)
            b = Modify(lc[q], k, a);
        else
            b = Modify(rc[q], k - sz[lc[q]] - 1, a);
        Insert(rt[q], seg, a, 1);
        Insert(rt[q], seg, b, -1);
        return b;
    }
    void Getarray(int q, int k, int tp)//搞出数组
    {
        if (!q)
            return;
        if (k <= sz[lc[q]])
        {
            Getarray(lc[q], k, tp);
            return;
        }
        A[tp][++ct[tp]] = rt[q];
        if (rc[q])
            A[!tp][++ct[!tp]] = rt[rc[q]];
        Getarray(rc[q], k - sz[lc[q]] - 1, tp);
    }
    int Query(int l, int r, int k, bool tp)//二分答案
    {
        if (l == r)
            return l;
        int sum = 0;
        for (int i = 1; i <= ct[0]; i++)
            sum -= S[S[A[0][i]].lc].sz;
        for (int i = 1; i <= ct[1]; i++)
            sum += S[S[A[1][i]].lc].sz;
        int mid = (l + r) >> 1;
        if (sum < k)
        {
            for (int i = 1; i <= ct[0]; i++)
                A[0][i] = S[A[0][i]].rc;
            for (int i = 1; i <= ct[1]; i++)
                A[1][i] = S[A[1][i]].rc;
            return Query(mid + 1, r, k - sum, tp);
        }
        for (int i = 1; i <= ct[0]; i++)
            A[0][i] = S[A[0][i]].lc;
        for (int i = 1; i <= ct[1]; i++)
            A[1][i] = S[A[1][i]].lc;
        return Query(l, mid, k, tp);
    }
    int Query(int l, int r, int k)//查询[l, r]之间的第k小
    {
        ct[0] = ct[1] = 0;
        Getarray(root, l - 1, 0);
        Getarray(root, r, 1);
        return Query(seg, k, 0);
    }
}T;
int main()
{
    for (int i = 20000000; i >= 1; i--)
        pool.push(i);
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        int a;
        scanf("%d", &a);
        T.Insert(T.root, i, a);
        T.Check(T.root, i);
    }
    scanf("%d", &m);
    int lastans = 0, x, y, k;
    char opt[10];
    for (int i = 1; i <= m; i++)
    {
        scanf("%s", opt);
        if (opt[0] == 'I')
        {
            scanf("%d%d", &x, &y);
            x ^= lastans;
            y ^= lastans;
            T.Insert(T.root, x, y);
            T.Check(T.root, x);
        }
        if (opt[0] == 'M')
        {
            scanf("%d%d", &x, &y);
            x ^= lastans;
            y ^= lastans;
            T.Modify(T.root, x, y);
        }
        if (opt[0] == 'Q')
        {
            scanf("%d%d%d", &x, &y, &k);
            x ^= lastans;
            y ^= lastans;
            k ^= lastans;
            printf("%d\n", lastans = T.Query(x, y, k));
        }
    }
}

你可能感兴趣的:(「BZOJ3065」带插入区间第K小值 替罪羊树×线段树)