zoukankan      html  css  js  c++  java
  • BZOJ 3065 带插入区间K小值

    声明:本人第一篇树套树blog , 由于看二逼平衡树的码量太大, 选择了这个题。结果发现是一道平衡树套权值线段树的题目。。心里mmp-ing.....

    题目描述

    从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i]。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间k小值。他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少。
    这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问。
    这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
    这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。(orz 主席树)
    这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
    请你帮一帮伏特吧。
    快捷版题意:带插入、修改的区间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 _x _y _k ------> 表示 Q _x^lastAns _y^lastAns _k^lastAns
    M _x _val  ------> 表示 M _x^lastAns _val^lastAns
    I _x _val  ------> 表示 I _x^lastAns _val^lastAns
    简单来说就是操作中输入的整数都要异或上一次询问的结果进行解码。

    (祝Pascal的同学早日转C++,就不提供pascal版的描述了。)

    输出

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

    样例输入

    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

    重点收获:

      经过一顿被D之后, 同届神犇JZYshuraK同学提出了一个问题:为什么对于这道题来说, 必须把平衡树放在外层, 不能放在内层? 像我这种蒟蒻。。连正解都搞不懂, 还去想其他算法为什么是“错”的?呵呵呵。。愉快的被挂了起来。另一同届神犇EM_LGH却随口说出了答案:因为线段树的形态改变了啊, 需要重构。(暗自拍大腿ing.....为什么这都想不到?)终于找到自己菜的诸多原因之一了:没有考虑事情的反面, 只关注正解, 却忽略了逐一排除错误思想的过程。

      据JZYshuraK同学介绍:这个东东其实也可以反过来, 不过要用什么ZKW线段树。。。

    题目讲解:

      我们考虑要维护的东西, 和题目中的操作:

      1.在数列中一个数之前插入一个数。

      2.修改一个数的值。

      3.查询$[l, r]$区间k小值。

      4.强制在线。

      单点插入操作需要一种平衡树来实现。 还得需要又一个维护无序序列的数据结构, 用来查询区间$k$小值。 平衡树需要放在外层, 且我们需要一种不太复杂(不用太多旋转的), 但是只需能维护单点信息的平衡树即可。显然替罪羊树比较适合。那么我们把替罪羊树放在外层。内层需要一个权值线段树。外层的替罪羊树每个节点维护自己对应的权值线段树和整棵子树所有权值的权值线段树。

      插入/修改时需要把经过的节点的权值线段树拿出来, 然后按照权值线段树的查询方式, 在树上二分即可。

      对于重构:当外层的替罪羊树达到重构条件时, 我们需要重构这棵树。优化:当多个地方需要重构时, 可以直接重构最上面的那个节点, 这样可以节省时间。

      另外, 这个题需要内存回收。

      附代码:

      

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define m 70000
    #define N 70010
    using namespace std;
    
    struct scg
    {
        int v, ls, rs, si, wr, tr;
    }a[N];
    struct seg
    {
        int ls, rs, si;
    }b[20000010];
    queue <int > q;
    char str[5];
    int root, n, pos[N], tot, qr[100], qt;
    void update(int p, int a, int l, int r, int &x)
    {
        if(!x)
        {
            x = q.front();
            q.pop();
        }
        b[x].si += a;
        if(!b[x].si)
        {
            x = 0;
            return ;
        }
        if(l == r)
            return ;
        int mid = (l + r) >> 1;
        if(p <= mid)
            update(p, a, l, mid, b[x].ls);
        else
            update(p, a, mid + 1, r, b[x].rs);
    }
    void del(int &x)
    {
        if(!x)
            return ;
        q.push(x);
        del(b[x].ls);
        del(b[x].rs);
        b[x].si = 0;
        x = 0;
    }
    int query(int k, int l, int r)
    {
        if(l == r)
            return l;
        int mid = (l + r) >> 1;
        int sum = 0;
        for(int i = 1;i <= qt;i ++)
            sum += b[b[qr[i]].ls].si;
        if(k <= sum)
        {
            for(int i = 1;i <= qt; i ++)
                qr[i] = b[qr[i]].ls;
            return query(k, l, mid);
        }
        else
        {
            for(int i = 1;i <= qt;i ++)
                qr[i] = b[qr[i]].rs;
            return query(k - sum, mid + 1, r);
        }
    }
    int build(int l, int r)
    {
        if(l > r)
            return 0;
        int mid = (l + r) >> 1;
        for(int i = l;i <= r; i ++)
            update(a[pos[i]].v, 1, 0, m, a[pos[mid]].tr);
        a[pos[mid]].ls = build(l, mid - 1);
        a[pos[mid]].rs = build(mid + 1, r);
        a[pos[mid]].si = r - l + 1;
        return pos[mid];
    }
    void dfs(int &x)
    {
        if(!x)
            return ;
        dfs(a[x].ls);
        pos[++ tot] = x;
        dfs(a[x].rs);
        a[x].si = 0;
        del(a[x].tr);
        x = 0;
    }
    void insert(int p, int &x, int v, bool flag)
    {
        if(!x)
        {
            x = ++ n;
            a[x].v = v;
            a[x].si = 1;
            update(v, 1, 0, m, a[x].wr);
            update(v, 1, 0, m, a[x].tr);
            return ;
        }
        bool tag;
        update(v, 1, 0, m, a[x].tr);
        a[x].si ++;
        if(p <= a[a[x].ls].si)
        {
            tag = ((a[a[x].ls].si + 1) * 4 > (a[x].si + 1) * 3);
            insert(p, a[x].ls, v, tag || flag);
        }
        else
        {
            tag = ((a[a[x].rs].si + 1) * 4 > (a[x].si + 1) * 3);
            insert(p - a[a[x].ls].si - 1, a[x].rs, v, tag || flag);
        }
        if(!flag && tag)
        {
            tag = 0;
            dfs(x);
            x = build(1, tot);
        }
    }
    int find(int p, int x)
    {
        if(p <= a[a[x].ls].si)
            return find(p, a[x].ls);
        else
            if(p > a[a[x].ls].si + 1)
                return find(p - a[a[x].ls].si - 1, a[x].rs);
        else
            return a[x].v;
    }
    void modify(int p, int x, int v1, int v2)
    {
        update(v1, -1, 0, m, a[x].tr);
        update(v2, 1, 0, m, a[x].tr);
        if(p <= a[a[x].ls].si)
            modify(p, a[x].ls, v1, v2);
        else
            if(p > a[a[x].ls].si + 1)
              modify(p - a[a[x].ls].si - 1, a[x].rs, v1, v2);
        else
        {
            del(a[x].wr);
            update(v2, 1, 0, m, a[x].wr);
            a[x].v = v2;
        }
    }
    void split(int b, int e, int x)
    {
        if(b <= 1 && e >= a[x].si)
        {
            qr[++ qt] = a[x].tr;
            return ;
        }
        if(b <= a[a[x].ls].si + 1 && e >= a[a[x].ls].si + 1)
            qr[++ qt] = a[x].wr;
        if(b <= a[a[x].ls].si)
            split(b, e, a[x].ls);
        if(e > a[a[x].ls].si + 1)
            split(b - a[a[x].ls].si - 1, e - a[a[x].ls].si - 1, a[x].rs);
    }
    int main()
    {
        for(int i = 1; i <= 20000000; i ++)
            q.push(i);
        scanf("%d",&n);
        for(int i = 1; i <= n; i ++)
        {
            scanf("%d", &a[i].v);
            update(a[i].v, 1, 0, m, a[i].wr);
            pos[i] = i;
        }
        root = build(1, n);
        int k;
        int las = 0;
        scanf("%d", &k);
        while(k --)
        {
            int x, y, z;
            scanf("%s%d%d", str, &x, &y);
            x ^= las;
            y ^= las;
            if(str[0] == 'Q')
            {
                qt = 0;
                split(x, y, root);
                scanf("%d", &z);
                z ^= las;
                printf("%d
    ", las = query(z, 0, m));
            }
            else
                if(str[0] == 'M')
                    modify(x, root, find(x, root), y);
            else
                insert(x - 1, root, y, 0);
        }
        return 0;
    }
    

      

     

  • 相关阅读:
    创建新进程,就三个函数CreateProcessAsUser CreateProcessWithLogonW CreateProcessWithTokenW(附网友的流程)
    一个简单的以User权限启动外部应用程序(用NetUserAdd函数和USER_INFO_1结构体动态添加用户,然后用CreateProcessWithLogonW启动程序)good
    将EXE作为资源,然后在释放到磁盘上并运行该exe程序(使用了FindResource,LoadResource,然后用CFile写成一个文件)
    CreateProcess启动隐藏的外部程序(其实就是CreateDesktop,然后指定STARTUPINFO.lpDesktop)
    封装业务函数
    SQLSERVER 数据库性能的的基本 MVC + EF + Bootstrap 2 权限管理
    Nutch搜索引擎Solr简介及安装
    C#程序的157个建议
    利用XCode来进行IOS的程序开发
    C#操作JSON
  • 原文地址:https://www.cnblogs.com/0724-zcsblog/p/10099015.html
Copyright © 2011-2022 走看看