zoukankan      html  css  js  c++  java
  • POJ 2104 K-th number

    Description

    You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
    That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
    For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

    Input

    The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
    The second line contains n different integer numbers not exceeding 109 by their absolute values --- the array for which the answers should be given.
    The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

    Output

    For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

    Sample Input

    7 3
    1 5 2 6 3 7 4
    2 5 3
    4 4 1
    1 7 3

    Sample Output

    5
    6
    3

    Hint

    This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

    Source

    Northeastern Europe 2004, Northern Subregion
     
    2104另每个初学主席树的人难以忘怀的题目。。。。
    主席树,又叫做函数式线段树,又叫做可持久化线段树,听起来就让人有一种想要跪地膜的感觉啊。。。。
    主席树是一种没学过就一点都不会但是学过了之后就能发挥很大作用的一种数据结构。。。
    主席树的本质其实是一种值域线段树。。。
    这题是对每个前缀维护一颗线段树。。。
    它的巧妙之处就是通过历史版本为转移(通过共用)来减少空间和时间的消耗,准确的理解历史版本转移,以及每个数组的意义对与解题很有帮助。。
    下面就直接蒯一份网上巨犇的理解吧。。。一字千金啊。。。讲得太好了。。。
     
     
    既然叫函数式线段树,那么就应该有跟普通线段树相同的地方。一颗线段树,只能维护一段区间里的元素。但是,每个询问的区间都不一样,若是对每段区间都单独建立的线段树,那~萎定了~。因此,就要想,如何在少建,或建得快的情况下,能利用一些方法,得出某个区间里的情况。
    比如一棵线段树,记为tree[i][j],表示区间[i,j]的线段树。那么,要得到它的情况,可以利用另外两棵树,tree[1][i-1]tree[1][j],得出来。也就是说,可以由建树的一系列历史版本推出。
    那么,怎么创建这些树呢?
    首先,离散化数据。因为如果数据太大的话,线段树会爆~~
    在所有树中,是按照当前区间元素的离散值(也就是用大小排序)储存的,在每个节点,存的是这个区间每个元素出现的次数之和(data域)。出现的次数,也就是存了多少数进来(建树时,是一个数一个数地存进来的)。
    先建议棵线段树,所有的节点data域为0。再一个节点一个节点地添加。把每个数按照自己的离散值,放到树中合适的位置,然后data+1,回溯的时候也要+1。当然,不能放到那棵空树中,要重新建树。第i棵树存的是区间(原序列)[1,i]。但是,如果是这样,那么会MLE+TLE。因此,要充分利用历史版本。用两个指针,分指当前空树和前一棵树。因为每棵树的结构是一样的,只是里面的data域不同,但是两棵相邻的树,只有一数只差,因此,如果元素要进左子树的话,右子树就会跟上个树这个区间的右子树是完全一样的,因此,可以直接将本树本节点的右子树指针接到上棵树当前节点的右儿子,这样即省时间,又省空间。
    每添加一个节点(也就是新建一棵树)的复杂度是O(logn),因此,这一步的复杂度是O(nlogn)
    建完之后,要怎么查找呢?
    跟一般的,在整棵树中找第k个数是一样的。如果一个节点的左权值(左子树上点的数量之和)大于k,那么就到左子树查找,否则到右子树查找。其实主席树是一样的。对于任意两棵树(分别存区间[1,i]和区间[1,j] i<j),在同一节点上(两节点所表示的区间相同),data域之差表示的是,原序列区间[i,j]在当前节点所表示的区间里,出现多少次(有多少数的大小是在这个区间里的)。同理,对于同一节点,如果在两棵树中,它们的左权值之差大于等于k,那么要求的数就在左孩子,否则在右孩子。当定位到叶子节点时,就可以输出了。

    鄙人的一些理解:所谓主席树呢,就是对原来的数列[1..n]的每一个前缀[1..i](1≤i≤n)建立一棵线段树,线段树的每一个节点存某个前 缀[1..i]中属于区间[L..R]的数一共有多少个(比如根节点是[1..n],一共i个数,sum[root] = i;根节点的左儿子是[1..(L+R)/2],若不大于(L+R)/2的数有x个,那么sum[root.left] = x)。若要查找[i..j]中第k大数时,设某结点x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在结点x内的数字总数。而对每一个前缀都建一棵树,会MLE,观察到每个[1..i]和[1..i-1]只有一条路是不一样的,那 么其他的结点只要用回前一棵树的结点即可,时空复杂度为O(nlogn)。

    Orz%%%%%%%%%%%%%%%%%%%

    附上与hzwer哈希值都差不多的代码QAQ。。。。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 using namespace std;
     6 const int N=100050;
     7 int sz,s[N*30],ls[N*30],rs[N*30],root[N*30],hash[N],a[N],num[N],tot,n,m;
     8 int find(int x)
     9 {
    10     int l=1,r=tot,mid;
    11     while(l<=r)
    12     {
    13         int mid=(l+r)>>1;
    14         if(hash[mid]<x) l=mid+1;
    15         else r=mid-1;
    16     }
    17     return l;
    18 }
    19 void insert(int l,int r,int x,int &y,int v)
    20 {
    21     y=++sz;s[y]=s[x]+1;
    22     ls[y]=ls[x],rs[y]=rs[x];
    23     if(l==r) return;
    24     int mid=(l+r)>>1;
    25     if(v<=mid) insert(l,mid,ls[x],ls[y],v);
    26     else insert(mid+1,r,rs[x],rs[y],v);
    27 }
    28 int query(int l,int r,int x,int y,int k)
    29 {
    30     if(l==r) return l;
    31     int mid=(l+r)>>1;
    32     if(s[ls[y]]-s[ls[x]]>=k) return query(l,mid,ls[x],ls[y],k);
    33     else return query(mid+1,r,rs[x],rs[y],k-(s[ls[y]]-s[ls[x]]));
    34 }
    35 int main()
    36 {
    37   int l,r,k;
    38   scanf("%d%d",&n,&m);
    39   for(int i=1;i<=n;i++) scanf("%d",&a[i]),num[i]=a[i];
    40   sort(num+1,num+1+n);
    41   hash[++tot]=num[1];
    42   for(int i=2;i<=n;i++) 
    43    if(num[i]!=num[i-1])
    44     hash[++tot]=num[i];
    45   for(int i=1;i<=n;i++) insert(1,tot,root[i-1],root[i],find(a[i]));    
    46   for(int i=1;i<=m;i++) 
    47   {
    48     scanf("%d%d%d",&l,&r,&k);
    49     printf("%d
    ",hash[query(1,tot,root[l-1],root[r],k)]);
    50   }
    51 }
  • 相关阅读:
    oracle创建表空间自增长和创建用户
    Cmd Markdown 简明语法手册
    Excel VBA修改不同文件簿sheet页名字
    常用JS(JQ)插件研究
    CSS颜色大全(转载)
    React框架学习
    不同浏览器中空格符的兼容问题
    VHDL----基础知识1
    串口通讯1---单片机
    Qt5 程序发布打包
  • 原文地址:https://www.cnblogs.com/qt666/p/6491176.html
Copyright © 2011-2022 走看看