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

树状数组优化求第K小和
本文介绍一种使用树状数组优化替代平衡树的方法,解决求整数序列所有子段和排序后的第K个元素的问题。通过二分答案并结合树状数组维护前缀和,实现高效查找小于目标值的元素数量。

http://blog.youkuaiyun.com/a2520123/article/details/8029799



为什么我看到一点题解又以为是主席树


问题描述:

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值