第k大区间和问题的树状数组实现

问题描述:

    给定一个整数序列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行每行一个整数。顺序给出序列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]j;
        if il 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]=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[n]:=0;rank[n]:=1;
        sort(1,n);s[0]:=maxlongint;
        for i:=1 to n do p[rank[i]]:=i;
        write(ans);
end.



BY QW

转载请注明出处

你可能感兴趣的:(第k大区间和问题的树状数组实现)