zoukankan      html  css  js  c++  java
  • zoj 2112 Dynamic Rankings(主席树&动态第k大)

    Dynamic Rankings

    Time Limit: 10 Seconds      Memory Limit: 32768 KB

    The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.

    Your task is to write a program for this computer, which

    - Reads N numbers from the input (1 <= N <= 50,000)

    - Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.


    Input

    The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.

    The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format

    Q i j k or
    C i t

    It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.

    There're NO breakline between two continuous test cases.


    Output

    For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])

    There're NO breakline between two continuous test cases.


    Sample Input

    2
    5 3
    3 2 1 4 7
    Q 1 4 3
    C 2 6
    Q 2 5 3
    5 3
    3 2 1 4 7
    Q 1 4 3
    C 2 6
    Q 2 5 3


    Sample Output

    3
    6
    3
    6


    (adviser)
    Site: http://zhuzeyuan.hp.infoseek.co.jp/index.files/our_contest_20040619.htm


    Author: XIN, Tao

    Source: Online Contest of Christopher's Adventure

    题意:

    给你一个长度为n(1 <= N <= 50,000)的序列。序列中每一个值都不超过1e9.然后有m(1 <= M <= 10,000)次操作。

    1.Q i j k  询问i,j间第k大的值。

    2.C i t     把序列的第i个值改成t。

    思路:

    明显的主席树。

    网上关于主席树的资料比較少。所以介绍下主席树。一方面让刚開始学习的人少走弯路。还有一方面方便自己温习。避免过段时间自己的代码都不认识了。。。

    先讲下主席树相关的概念吧。

    1.什么是主席树。

    主席树貌似是网上流传的一种叫法。貌似学名叫函数式线段树。相关概念能够自行百度。

    2.主席树有什么用。

    主席树能够求解区间第k大问题。当然这个划分树也能够完毕且时间和空间都比主席树更优。那学主席树还有什么用。当然实用啦。划分树仅仅能解决静态第k大问题。也就是说。假设序列里的值有更新的话划分树就不再适用了。这个时候就体现主席树的优势了。主席树的其它应用还没研究。等遇到了再补充。

    3.主席树究竟是什么。

    事实上所谓的主席树就是一堆线段树。一堆线段树?一颗线段树就耗那么多内存了。一堆不会爆么?这个就是主席树的精华了。后面再解释。

    4.主席树是怎么实现查询区间第k大问题的呢?

            先从最主要的问题入手吧。假设区间是固定的就为[1,n]。然后询问区间的第k大。非常easy吧。排个序即可了,可是我们要讲的是线段树的做法。考虑到序列的值可能非常大。我们能够先对这n个值hash一下映射到1-n的范围内。然后我们就把这n个值插入线段树。线段树的每一个结点维护子树中已经插入值的个数。那么查找第k大就非常easy了。假设k>左子树值的个数。那么就在右子树中找第(k-左子树值个数)大值。否则在右子树中找。递归进行直到到叶子结点。然后还原hash值即可了。

            如今关键就是怎么解决区间不是[1,n]的问题了。假如我们要查询区间[l,r]的第k大值。假设我们有一颗线段树R插入了里面的值。当然就跟区间[1,n]的方法一样了。假如我们建了n棵上面的线段树。第i棵插入了[1,i]的值。感觉前缀的思想真的非常巧妙。跟字符串的前缀有异曲同工之妙。第i棵线段树的根为T[i]。那么怎么求[l,r]的第k大呢。事实上和上面方法差点儿相同。我们关键就是要知道线段树R区间[l,r]内的值在左子树的值有多少个。在右子树有多少个。这个时候前缀的优势就来了。(T[r]左子树值的个数-T[l-1]左子树值的个数)不就是R在左子树值的个数么。然后递归定位到叶子结点即可了。

            但问题又来了。那么多棵线段树。就算内存不爆。建树的时间也该爆了吧。精华部分来了。不得不叹服前人的智慧啊。因为这n棵线段树结构形态一致。结点个数都一样。这个非常好理解吧。都是维护[1,n]值出现个数嘛。而更加爽的是。T[i+1]比T[i]多插一个值a[i+1].试想假设a[i+1]被插入了T[i+1]的左子树。那么T[i+1]的右子树和T[i]的左子树将全然同样。进入右子树同理。那所以T[i+1]的右子树全然不用建了。直接把T[i+1]右子树指针指向T[i]右子树即可了。共享结点。这是多么机智。一下攻克了一半的结点。这种做法递归进行。也就是T[i+1]仅仅需建log2(n)的结点。这样就能够解决静态第k大了。

    能够拿这个题开刀嘿嘿~

            如今剩下的就是解决动态第k大问题了。这个地方我理解了非常久。才明确。明确了事实上非常easy。假设我们更新了arr[i]那么它将会影响T[i]~T[n]。难道我们要一个一个改么。这样改显然时间上不同意。可是你会发现它的改变对T[i]~T[n]的影响是一样的。假设我们把影响(做子树添加多少值。右子树添加多少结点)记录下来。那我们就仅仅需用原始值减去变化值就好了。变化值我们能够用树状数组来记录。我们把变化当作一个值,那么就成了单点更新区间求和问题了。仅仅是这里不是一个值而是一个线段树而已,可是我们能够类似的处理。

    也就是每一个树状数组的结点都是一棵线段树。哈哈。真是刺激。那么这个问题就圆满攻克了。分析下时空复杂度。首先建一棵空树m*log2(m)。m为hash后值的个数。然后建n个树.n*log2(m)。然后q次查询操作.2*q*log2(m).所以总时间复杂度为。O((m+n+2*q)*log2(m))。空间复杂度。4*m+n*lon2(m)。

    以下附上代码:

    #include<algorithm>
    #include<iostream>
    #include<string.h>
    #include<sstream>
    #include<stdio.h>
    #include<math.h>
    #include<vector>
    #include<string>
    #include<queue>
    #include<set>
    #include<map>
    using namespace std;
    const int INF=0x3f3f3f3f;
    const int maxn=60010;
    const int maxm=2500010;
    int ls[maxm],rs[maxm],c[maxm];//ls,rs左右儿子指针。c存值的个数
    int arr[maxn],H[maxn],T[maxn];//arr存原序列.H存排序后值。T[i]第i棵线段树的根
    int s[maxn],ua[maxn],ub[maxn],*use;//s为树状数组结点。当然也是线段树的根啦。
    int n,m,tot;
    struct node
    {
        int l,r,k;
    } qs[10010];//因为要先hash。
    void init()//hash初始化
    {
        sort(H,H+m);
        m=unique(H,H+m)-H;
    }
    int Hash(int x)
    {
        return lower_bound(H,H+m,x)-H;
    }
    int build(int L,int R)//建空树
    {
        int rt=tot++,mid;
        c[rt]=0;
        if(L!=R)
        {
            mid=(L+R)>>1;
            ls[rt]=build(L,mid);
            rs[rt]=build(mid+1,R);
        }
        return rt;
    }
    int Insert(int prt,int x,int val)
    {
        int nrt=tot++,tp=nrt,l=0,r=m-1,mid;
        c[nrt]=c[prt]+val;
        while(l<r)//非递归插入。节省内存。
        {
            mid=(l+r)>>1;
            if(x<=mid)
            {
                ls[nrt]=tot++,rs[nrt]=rs[prt];//共享结点
                prt=ls[prt],nrt=ls[nrt];
                r=mid;
            }
            else
            {
                ls[nrt]=ls[prt],rs[nrt]=tot++;
                prt=rs[prt],nrt=rs[nrt];
                l=mid+1;
            }
            c[nrt]=c[prt]+val;
        }
        return tp;
    }
    int lowbit(int x)
    {
        return x&(-x);
    }
    void update(int x,int p,int d)//树状数组更新
    {
        while(x<=n)
        {
            s[x]=Insert(s[x],p,d);
            x+=lowbit(x);
        }
    }
    int sum(int x)
    {
        int ret=0;
        while(x)
        {
            ret+=c[ls[use[x]]];
            x-=lowbit(x);
        }
        return ret;
    }
    int qu(int L,int R,int k)
    {
        int lrt=T[L-1],rrt=T[R],l=0,r=m-1,mid,tp,i,sa,sb;
        for(i=L-1,use=ua;i;i-=lowbit(i)) use[i]=s[i];
        sb=sum(L-1);
        for(i=R  ,use=ub;i;i-=lowbit(i)) use[i]=s[i];
        sa=sum(R);
        while(l<r)
        {
            mid=(l+r)>>1;
            tp=sa-sb+c[ls[rrt]]-c[ls[lrt]];//初始值加改变值
            if(k<=tp)
            {
                r=mid;
                lrt=ls[lrt],rrt=ls[rrt];
                for(i=L-1,use=ua;i;i-=lowbit(i)) use[i]=ls[use[i]];//计算相应子树改变
                sb=sum(L-1);
                for(i=R  ,use=ub;i;i-=lowbit(i)) use[i]=ls[use[i]];
                sa=sum(R);
            }
            else
            {
                l=mid+1;
                k-=tp;
                lrt=rs[lrt],rrt=rs[rrt];
                for(i=L-1,use=ua;i;i-=lowbit(i)) use[i]=rs[use[i]];
                sb=sum(L-1);
                for(i=R  ,use=ub;i;i-=lowbit(i)) use[i]=rs[use[i]];
                sa=sum(R);
            }
        }
        return l;
    }
    int main()
    {
        int i,q,cas;
        char op[10];
        scanf("%d",&cas);
        while(cas--)
        {
            scanf("%d%d",&n,&q);
            tot=m=0;
            for(i=1;i<=n;i++)
                scanf("%d",&arr[i]),H[m++]=arr[i];
            for(i=0;i<q;i++)
            {
                scanf("%s",op);
                if(op[0]=='Q')
                    scanf("%d%d%d",&qs[i].l,&qs[i].r,&qs[i].k);
                else
                {
                    scanf("%d%d",&qs[i].l,&qs[i].r);
                    qs[i].k=-INF,H[m++]=qs[i].r;
                }
            }
            init();
            T[0]=build(0,m-1);
            for(i=1;i<=n;i++)
                T[i]=Insert(T[i-1],Hash(arr[i]),1);
            for(i=1;i<=n;i++)
                s[i]=T[0];
            for(i=0;i<q;i++)
            {
                if(qs[i].k==-INF)
                {
                    update(qs[i].l,Hash(arr[qs[i].l]),-1);
                    update(qs[i].l,Hash(qs[i].r),1);
                    arr[qs[i].l]=qs[i].r;//開始忘了改这里无限wa啊。。。
                }
                else
                    printf("%d
    ",H[qu(qs[i].l,qs[i].r,qs[i].k)]);
            }
        }
        return 0;
    }
    


  • 相关阅读:
    VS2010/MFC编程入门之三(VS2010应用程序工程中文件的组成结构)
    VS2010/MFC编程入门之二(利用MFC向导生成单文档应用程序框架)
    VS2010/MFC编程入门之一(VS2010与MSDN安装过程图解)
    Getmemory问题
    计算后缀表达式
    0-1背包问题
    不抛异常的swap函数
    输出n*n矩阵
    字符串全排列输出
    判断主机字节
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4022993.html
Copyright © 2011-2022 走看看