Rq-350【归并树】

来源:互联网 发布:淘宝店铺欢迎语幽默 编辑:程序博客网 时间:2024/04/29 15:01

题目:找第k小的数

问题编号:350

题目描述

给出一个长度为N的序列A1,A2,A3,...,AN,其中每项都是
小于10^5的自然数。
现在有M个询问,每个询问都是Ai...Aj中第k小的数等
于多少。
数据范围:
在60%的数据中,1≤N≤1000,1≤M≤1000
在100%的数据中,1≤N≤10000,1≤M≤2000

Darkmaster说:“这题水吧?水了就得给我AC了看看,呵呵!”

输入格式

第一行两个正整数N,M。

第二行N个数,表示序列A1,A2,...,AN。

紧着的M行,每行三个正整数i,j,k(k≤j-i+1),表示
询问Ai...Aj中第k小的数等于多少。

输出格式

共输出M行,第i行输出第i个询问的答案。

==================================================================

按题目给的数据规模,目测记下元素下标跑个快排然后朴素(O(nm+nlog2n))就能过,但作者显然不是这个意思,求区间kth数有一类现成的数据结构。


比如这题,我们可以归并树,其基本思想就是用log2n层的检索,记下归并排序的过程,每次查找区间[l,r]的kth数时,设x=kth,我们可以算出x在区间[l,r]的排名,然后二分枚举即可;至于怎么求x在[l,r]内的排名,可以将[l,r]拆解为若干个子区间(子区间存在于归并的过程中),将每个子区间内小于x的元素数量累加起来即可;至于怎么在子区间内累加,由于子区间是有序的,再来一次二分就可以了。

综上,归并树的空间复杂度为 nlog2n ,时间复杂度为 (log2n)^3。

如果用归并树来实现这一问题的话,当数据规模n<=10^6时可以在1s内出解。

==================================================================

先贴个代码:

varg:array[1..20,1..10000] of longint;a:array[1..10000] of longint;n,m:longint;procedure fsetup(x,l,r:longint);var mid,i,j,k:longint;begin     if l=r then g[x,l]:=a[l]     else begin          mid:=(l+r)shr 1;          fsetup(x+1,l,mid);          fsetup(x+1,mid+1,r);          j:=l; k:=mid+1;          for i:=l to r do              if (k<=r)and((j>mid)or(g[x+1,k]<g[x+1,j])) then begin                 g[x,i]:=g[x+1,k];                 inc(k);              end else begin                 g[x,i]:=g[x+1,j];                 inc(j);              end;     end;end;function xth(x,l,r,v:longint):longint;var mid:longint;begin     if l>r then exit(0);     mid:=(l+r)shr 1;     if g[x,mid]<v then xth:=mid-l+1+xth(x,mid+1,r,v)     else xth:=xth(x,l,mid-1,v)end;function th(x,ll,rr,l,r,v:longint):longint;var mid:longint;begin     if (ll=l)and(rr=r)then exit(xth(x,l,r,v))     else begin          mid:=(ll+rr)shr 1;          if r<=mid then exit(th(x+1,ll,mid,l,r,v));          if l>mid  then exit(th(x+1,mid+1,rr,l,r,v));          exit(th(x+1,ll,mid,l,mid,v)+th(x+1,mid+1,rr,mid+1,r,v));     end;end;function query(l,r,k:longint):longint;var low,high,mid:longint;begin     low:=0;     high:=100000;     while low<high do begin           mid:=((low+high)shr 1)+1;           if th(1,1,n,l,r,mid)<k then low:=mid           else high:=mid-1;     end;     exit(low);end;procedure main;var i,p,q,r:longint;begin     readln(n,m);     for i:=1 to n do read(a[i]); readln;     fsetup(1,1,n);     for i:=1 to m do begin         readln(p,q,r);         writeln(query(p,q,r));     end;end;begin     main;end.
==================================================================

应当指出,由于题目限定了元素的取值范围是非负的,所以在对x的二分上直接采用了shr,而在另外一道类似题目上(数据取值范围是-Inf~Inf),笔者采取同样的方式处理结果挂的很惨(把符号位直接挪了过来~囧)。

其实还有一种和归并树很相似的数据结构-划分树,这个等我A掉另外那道题再说吧!