zhugeliang 题解

本文解析了一道算法题,通过一维前缀和及贪心策略求解数组中连续子数组的第m大绝对收益。讨论了50分算法的局限,并提出一种高效算法,使用最大堆维护候选答案,最终实现O(n log n + k log k)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

据说这是一道内部题,所以这里没有办法贴上题目和数据,那些做到这道题的自行来看就可以了orz

好,对于接近满满一张纸的题目描述来捋一下题目要求:

n张牌,每张牌都有一个收益v[i],连续取任意张牌,求出最大收益、次打收益……第m大收益,收益值为绝对值

下面开始说思路。

这道题看上去其实还是很容易上手的,

第一,一般都能看出来要用到一维前缀和,利用一维前缀和我们能直到任意[l,r]之间的收益和(并没有加绝对值)

第二,利用贪心,我们会发现由于我们的前缀和是可能带负数的,而我们需要的是最大到第m大收益,如何让收益最大呢?这答案就可能出现在一下两种情况:

(1)直接从第1张开始选,到第x张,即前缀和的绝对值

(2)不从第一张开始选,从任意[l,r]选取,要让收益最大到次打的话,

答案可能出现的范围一定是:前m大前缀和和前m小前缀和两两组合做差的结果

易知,我们需要对前缀和进行排序

=w=

分水岭来了,容易想到也容易做出来的是50分算法,即排序后,开一个数组存abs(sum[i]),然后枚举前k大前缀和和前k小前缀和做差,再排序取前m个答案输出,时间复杂度为O(nlogn+k^2),

根据数范围 50%的1<=k<=1000,,100% 1<=n<=100000,1<=k<=n,期望得分,50分

那么如何优化O(k^2)呢 0.0

假如我们取出的最大值是我们选的第i大的前缀和-第j小的前缀和,记做(i,j),那么下一个不从第一张取的答案范围一定是(i-1,j) 和 (i,j+1) 中的一个,所以我们发现我们每当取出最大值得时候,我们选择答案的范围就有所变化,我们需要选择一个高效的方法来维护这个变化

显然,我们要用最大堆=。=

那么我们没取出一个(i,j)就要加进去一个(i-1,j)和(i,j+1),很显然会有重复的在堆里捣乱,怎么避免重复呢?

我们先把abs(sum[i])都存到堆里后,我们选择第一大的前缀和即排序后的sum[n]与所有比它小的sum[i]做差,加入堆里,这样我们没取出一个(i,j) 只需要加入 一个(i-1,j) 就可以了(直接替换),保证了不重不漏=w=

取出m次堆首(最大堆),放到ansn数组里,再进行排序,然后从大到小输出即可=w=

注意:

1、前缀和sum数组中是没有绝对值的,而在选取最大值的时候,堆里要加入的是取完绝对值以后的

2、虽然我们每次取最大堆堆首但是那只是当前最大值,更新答案范围后可能会出现更大值,所以仍需要一遍排序

3、对于取出的数是(i,j)的我们要进行上述处理,那如果取出的数是前缀和的绝对值呢?显然我们只需要把这个数从堆里删去就可以了

4.在进行替换更新的时候,要注意同时更新(i,j)->(i-1,j)

5、所谓前m大的前缀和,就是排序后的sum[n-m+1]~sum[n],前m小的前缀和就是排序后的sum[1]~sum[m]

var
    n,m,size,ans        :longint;
    i                   :longint;
    a,sum,ansn          :array[0..100010] of longint;
    h,ll,rr             :array[0..200010] of longint;
procedure swap(var a,b:longint);
var
    c:longint;
begin
   c:=a;a:=b;b:=c;
end;

procedure sort(l,r: longint);
var
         i,j,x,y: longint;
begin
         i:=l;
         j:=r;
         x:=sum[(l+r) div 2];
         repeat
           while sum[i]<x do inc(i);
           while x<sum[j] do dec(j);
           if not(i>j) then
             begin
                y:=sum[i];sum[i]:=sum[j];sum[j]:=y;
                inc(i);j:=j-1;
             end;
         until i>j;
         if l<j then sort(l,j);
         if i<r then sort(i,r);
end;

procedure sort2(l,r: longint);
var
         i,j,x,y: longint;
begin
         i:=l;
         j:=r;
         x:=ansn[(l+r) div 2];
         repeat
           while ansn[i]<x do inc(i);
           while x<ansn[j] do dec(j);
           if not(i>j) then
             begin
                y:=ansn[i];ansn[i]:=ansn[j];ansn[j]:=y;
                inc(i);j:=j-1;
             end;
         until i>j;
         if l<j then sort(l,j);
         if i<r then sort(i,r);
end;
//
procedure heapdown(i:longint);
var
    t:longint;
begin
   while (i*2<=size) do
   begin
      if (h[i*2]>h[i]) then t:=i*2 else t:=i;
      if (i*2+1<=size) then
       if (h[t]<h[i*2+1]) then t:=i*2+1;
      if (t<>i) then
      begin
         swap(h[i],h[t]);
         swap(ll[i],ll[t]);
         swap(rr[i],rr[t]);
         i:=t;
      end else exit;
   end;
end;

begin
   assign(input,'zhugeliang.in');reset(input);
   assign(output,'zhugeliang.out');rewrite(output);
   read(n,m);
   for i:=1 to n do read(a[i]);
   for i:=1 to n do sum[i]:=sum[i-1]+a[i];
   sort(1,n);
   //
   for i:=1 to n do h[i]:=abs(sum[i]);
   for i:=n+1 to m+n do
   begin
      h[i]:=sum[n]-sum[i-n];
      ll[i]:=i-n;
      rr[i]:=n;
   end;
   size:=n+m;
   for i:=size div 2 downto 1 do heapdown(i);
   //
   while (ans<m) do
   begin
      inc(ans);
      ansn[ans]:=h[1];
      if (ll[1]=0) and (rr[1]=0) then
      begin
         h[1]:=h[size];
         rr[1]:=rr[size];
         ll[1]:=ll[size];
         dec(size);
         heapdown(1);
      end else
      begin
         h[1]:=sum[rr[1]-1]-sum[ll[1]];
         rr[1]:=rr[1]-1;
         heapdown(1);
      end;
   end;
   sort2(1,m);
   for i:=m downto 1 do writeln(ansn[i]);
   close(input);close(output);
end.
                                    ——by Eirlys

转载请注明出处=w=

orz orz orz orz orz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值