第k大区间和问题的树状数组实现
2012-09-28 20:08
197 查看
问题描述:
给定一个整数序列a[1..N],定义sum[i][j]=a[i]+a[i+l]+……+a[j],将所有的sum[i][j]从小到大排序(其中i,j满足1<=i<=j<=N),得到一个长为N*(N+1)/2的序列,求该序列中的第k个元素。输入格式(ktm.in)
第一行有两个整数N,k,其中0<N<=20000,1<=k<=N*(N+1)/2,数据保证任何一个sum[i][j]的绝对值不超过2^30。
接下来N行每行一个整数。顺序给出序列a的元素。
输出格式(kth.out)
sum序列中的第k个元素
题解:
这道题据说是noip难度的,如果noip真的考这个我就可以直接退役了.
我讲这道题主要是想讲如何用树状数组来代替平衡树的部分功能,从而节省代码量.
算法是很显然的:首先二分答案.假设答案为k,我们求出所有sum中小于k的个数就行了.算法的瓶颈在于什么找到所有sum中有多少小于k.
先讲讲平衡树的做法:预处理一个s数组,s[i]表示sum[0,i].依次加入s[i],统计s1~s[i-1]中大于s[i]-k的个数即可.利用一个平衡树就可以很简单的做到这些操作.
然而,平衡树不仅代码量较大且常数很高,所以我们考虑利用其他数据结构来实现这一功能.
这个数据结构要支持两种操作:加入:加入一个元素;查找:查找所有元素中比k小的元素个数.
这个东西貌似只有平衡树之类的高级数据结构能做,但是,我们可以只用一个树状数组就实现这些功能.
我们先将所有的s值排序.首先按原先的次序依次加入si.先将插入操作.插入时直接将si当前对应的下标置为1即可(初值为0).而加入si前,我们要查找在si之前的元素中大于si-k的个数.我们可以先在s中二分出一个p,使s[p]之前的元素都大于si-k,然后求出1~p中的前缀和即可.前缀和可以用树状数组维护.
这个方法虽然多了一个二分操作和一个排序.但是排序相信大家都能在5分钟内搞出来.至于二分也非常简单.而树状数组的代码量远小于平衡树,并且几乎不会打错,所以这个方法还是很实用的.至于时间复杂度,虽然算法多了一个二分的复杂度log(n),但不要忘记,树状数组的常数远小于平衡树,空间消耗也非常小.所以这种处理方式不会差与平衡树,甚至在某些情况下比平衡树更优.
下面是我的代码
program kth; type int=longint; var i,j,k,m,n:int; s,f,p,rank:array[0..100000]of int; x,y,z:int; procedure swap(var x,y:int);var t:int; begin t:=x;x:=y;y:=t; end; procedure sort(l,r:int);var i,j:int; begin i:=l;j:=r; x:=s[l+random(r-l)]; repeat while s[i]>x do inc(i); while s[j]<x do dec(j); if i<=j then begin swap(s[i],s[j]);swap(rank[i],rank[j]); inc(i);dec(j); end; until i>j; if i<r then sort(i,r); if j>l then sort(l,j); end; function ask(x:int):int;var i:int; begin ask:=0;i:=1; while i<=x do begin while i+(i and -i)<=x do i:=i+(i and -i); inc(ask,f[i]);inc(i); end; end; procedure ins(x:int); begin while x<=n do begin inc(f[x]);x:=x+(x and -x); end; end; function get(min:int):int;var l,r,mid,i:int; begin l:=1;r:=n; while r-l>3 do begin mid:=(l+r)>>1; if s[mid]>=min then l:=mid else r:=mid; end; for i:=l to r do if(s[i-1]>=min)and(s[i]<min)then exit(i-1); exit(n); end; function sum(max:int):int;var i,j,k:int; begin sum:=0; fillchar(f,sizeof(f),0); for i:=1 to n do begin j:=p[i];k:=get(s[j]-max); inc(sum,ask(k));ins(j); end; end; function ans:int;var l,r,mid:int; begin l:=s -s[1];r:=s[1]-s ; while true do begin mid:=trunc((l+r)/2); if sum(mid)>=m then r:=mid else l:=mid; if r-l<10 then begin for i:=l-1 to r do begin k:=sum(i); if k>=m then exit(i); end; end; end; end; begin read(n,m);s[0]:=0; for i:=1 to n do begin read(x);s[i]:=s[i-1]+x;rank[i]:=i+1; end; inc(n);s :=0;rank :=1; sort(1,n);s[0]:=maxlongint; for i:=1 to n do p[rank[i]]:=i; write(ans); end.
BY QW
转载请注明出处
相关文章推荐
- 【第k大区间和问题的树状数组实现】
- 用树状数组解决求区间最值的问题:hdu1754
- 用树状数组解决区间查询问题
- 树状数组实现 区间修改+区间查询
- 用树状数组解决区间查询问题
- 北京网赛I题 hiho1391 (树状数组、区间覆盖最大值问题)
- 用树状数组解决区间查询问题
- 动态区间第k小(主席树+线段树套树状数组)
- hdu1556区间问题(前缀和,树状数组,线段树)
- POJ 2761 Feed the dogs(树状数组求区间第K大)
- ARC 068E Snuke Line 区间问题+树状数组
- 树状数组求区间最大值一类问题
- hdu 3727 Jewel 划分数+树状数组 求区间和当前段的第k大数
- [二分+树状数组]51 Nod 1685——第K大区间2
- [BZOJ 1901][ZOJ 2112]Dynamic Rankings(树状数组套主席树、动态区间第k大值查询)
- 区间问题:两道线段树和树状数组的基础练习
- 树状数组关于区间修改区间求和的问题
- 区间查询(树状数组之差点问线问题)
- ACM学习历程—51NOD 1685 第K大区间2(二分 && 树状数组 && 中位数)
- 用树状数组解决区间查询问题