【划分树】求区间K大数

       求区间K大数,众所周知有二分答案+树套树的做法,每个询问复杂度为O(log3N),可谓近似一个O(N)了,不仅时间复杂度高,编程复杂度也不低,后来才发现还有一种叫划分树的数据结构,专门做这种问题。

      本来觉得知道个树套树就够了,据我所知划分树还不支持区间修改操作,但是这次NOI一试的piano偏偏那出题人就有这么重口味,给你来两个500000的点!真是不简单,当时我都在想有没有O(N)的算法了。树套树的复杂度偏偏就被卡掉了,三个点平均每个点要跑5秒,好吧,那就学学划分树把。

      看了划分树以后觉得是继后缀数组以来第二巧妙的数据结构,划分树中的节点代表的区间并不是原数组的区间,而是在排序以后数组的区间,通过分治的思想解决问题。

      刚开始看划分树还觉得比较飘,对于每一个区间中的每一个节点,要记录一个从这个节点到这个节点所在的区间的左端点之间有多少个进入了左子树(这个区间中前mid小的数进入左子树,其他右子树),这个值很关键,我就可以O(1)的知道我所查找的区间的数是在左子树还是在右子树了。

 

      建树很好写,主要是查找的式子比较长,下面简略的写下:

(rs是区间左端点到我要查询的右端点之间进入左子树的个数,ls是区间左端点到我要查询的区间左端点之间进入左子树的个数,因此我就可以知道我要查询的区间有多少进入左子树的,从而知道我是要递归进左子树查询还是递归进右子树查询)

if rs-ls<k then find:=find(h+1,mid,rr,l-ll-ls+mid,r-ll-rs+mid,k-rs+ls) else find:=find(h+1,ll,mid-1,ls+ll,rs+ll-1,k);

 

代码依旧玩缩行流,60行,happy~happy(这道题是poj2104)

program poj2104; const maxn=100000; var a,b:array[0..maxn] of longint; s,d:array[1..18,0..maxn] of longint; n,m,i,x,y,z,ls,rs,mid,p:longint; procedure sort(l,r:longint); var i,j,x,y:longint; begin i:=l;j:=r;x:=a[(i+j)>> 1]; repeat while b[a[i]]<b[x] do inc(i); while b[a[j]]>b[x] do dec(j); if i<=j then begin y:=a[i];a[i]:=a[j];a[j]:=y; inc(i);dec(j); end; until i>j; if l<j then sort(l,j); if i<r then sort(i,r); end; procedure buildtree(h,l,r:longint);var mid:longint; begin if l=r then exit; mid:=(l+r+1)>> 1;p:=0; for i:=l to r do if d[h,i]<mid then begin d[h+1,l+p]:=d[h,i]; inc(p);s[h,i]:=p; end else begin d[h+1,mid+i-l-p]:=d[h,i]; s[h,i]:=p; end; buildtree(h+1,l,mid-1); buildtree(h+1,mid,r); end; function find(h,ll,rr,l,r,k:longint):longint; begin if l=r then exit(d[h,r]); if l=ll then ls:=0 else ls:=s[h,l-1]; rs:=s[h,r];mid:=(ll+rr+1)>> 1; if rs-ls<k then find:=find(h+1,mid,rr,l-ll-ls+mid,r-ll-rs+mid,k-rs+ls) else find:=find(h+1,ll,mid-1,ls+ll,rs+ll-1,k); end; begin readln(n,m); for i:=1 to n do begin read(b[i]);a[i]:=i; end; sort(1,n); for i:=1 to n do d[1,a[i]]:=i; buildtree(1,1,n); for i:=1 to m do begin readln(x,y,z); writeln(b[a[find(1,1,n,x,y,z)]]); end; end.

你可能感兴趣的:(数据结构,编程,算法)