取数游戏2--牛客网

这是一篇关于动态规划的博客,分析了牛客网上的一道题目,目标是最大化两个整数序列中每次取数的价值之和。文章详细介绍了如何通过动态规划解决这个问题,包括边界条件、最优子结构和状态转移方程,并提供了AC代码。

链接:https://ac.nowcoder.com/acm/problem/14701
来源:牛客网
 

题目描述

给定两个长度为n的整数列A和B,每次你可以从A数列的左端或右端取走一个数。假设第i次取走的数为ax,则第i次取走的数的价值vi=bi⋅ax,现在希望你求出∑vi的最大值。

输入描述:

第一行一个数T,表示有T组数据。
对于每组数据,第一行一个整数n,
接下来两行分别给出A数列与B数列。

输出描述:

每一组数据输出一行,最大的∑vi。

示例1

输入

2
2
1 1000
2 1
5
1 3 5 2 4
1 2 3 4 5

输出

2001
52

说明

对于第二个样例,
第一次从左边取走a1,v1=a1⋅b1=1,
第二次从左边取走a2,v2=a2⋅b2=6,
第三次从右边取走a5,v3=a5⋅b3=12,
第四次从右边取走a4,v4=a4⋅b4=8,
第五次取走剩下的a3,v5=a3⋅b5=25。
总价值∑vi=1+6+12+8+25=52

数据规模:

T≤10

1≤n≤10^3

1≤ai,bi≤10^3

 

分析:

标签是动态规划。首先我们先考虑最小的情况:

<think>题目要求:动态查询第k小的,有添加元素的操作。 我们可以使用两个堆:一个大顶堆(存储较小的k个一个小顶堆(存储较大的)。 具体思路: - 维护一个大顶堆max_heap,用于存储当前最小的k个(堆顶是这k个中最大的,即第k小的- 维护一个小顶堆min_heap,用于存储其他比max_heap中大的- 当添加一个时: 如果当前max_heap的大小小于k,则直接加入max_heap(注意:加入后如果超过k,需要将堆顶弹出到min_heap中) 如果当前max_heap的大小已经达到k,则比较新元素与max_heap堆顶的大小: 如果新元素小于max_heap堆顶,说明它可以成为前k小,那么将max_heap堆顶弹出(放入min_heap),并将新元素加入max_heap。 否则,将新元素加入min_heap。 - 但是注意,题目中k是固定的,我们每次查询时,max_heap的堆顶就是答案(因为max_heap中存放了最小的k个,堆顶是其中最大的,所以就是第k小)。 - 然而,操作过程中,我们可能会添加多个元素,导致max_heap中的元素个可能超过k者不足k。 重新梳理: - 我们的目标是:max_heap中始终保存当前最小的k个(堆顶是第k小),当max_heap的大小超过k时,我们需要将堆顶弹出到min_heap(这样max_heap的大小又变成k,堆顶就是第k小)。 - 但是,在添加元素的过程中,可能先添加的元素不够k个,那么我们就需要先填满max_heap(达到k个),然后后续添加的元素再进行比较调整。 具体步骤: 1. 初始化:将初始数组的前k个元素放入max_heap(大顶堆),然后将剩余的元素放入min_heap(小顶堆)。但是注意:初始数组可能有重复,而且可能不足k个? 2. 题目要求:如果没有k个就输出-1。所以我们在查询时,如果当前总元素个(包括初始添加的)小于k,则输出-1。 3. 但是,我们如何维护两个堆? - 我们设max_heap为大顶堆,存储最小的k个(当元素总>=k时,我们保证max_heap的大小为k;当元素总<k时,max_heap的大小为当前总元素个,此时查询应该输出-1?但是题目要求:如果没有k个就输出-1,所以查询时先判断总元素个是否小于k,如果是,则输出-1;否则,max_heap的堆顶就是答案。 - 但是,我们还需要注意:在添加元素的过程中,可能使得总元素个达到k,那么我们就需要将min_heap中的一些元素调整到max_heap中吗?实际上,我们并不需要立即调整,因为我们在添加元素时已经做了调整。 然而,上述方法在添加元素时,如果当前总元素个小于k,我们直接加入max_heap,此时max_heap的大小就是当前总元素个。当总元素个达到k时,我们开始维护:后续添加的元素,如果大于max_heap堆顶,则加入min_heap;否则,将当前元素加入max_heap,然后将max_heap堆顶弹出到min_heap(这样max_heap的大小保持为k,且堆顶是当前第k小)。 但是,这里有一个问题:在初始阶段,我们可能没有k个元素,然后我们不断添加元素直到超过k个。那么,在添加第一个元素时(假设初始有n0个元素,n0<k),我们添加一个元素后,总元素个为n0+1,此时max_heap的大小为n0+1(小于k),所以不需要弹出。当添加元素直到总元素个达到k时,我们开始维护max_heap的大小为k。 所以,我们可以这样设计: -一个变量total记录当前总元素个(初始为n,然后每次添加操作加1)。 - 初始化: max_heap = [] # 大顶堆,用负存储来实现大顶堆(者使用heapq的小顶堆,但是存负) min_heap = [] # 小顶堆,正常存储 将初始数组排序,然后前min(n, k)个放入max_heap(注意,如果n<k,则全部放入max_heap,min_heap为空) 如果n>k,那么将剩下的n-k个放入min_heap。 - 但是,注意:我们并不需要一开始就排序整个数组,我们可以用堆来调整。 for i in range(n): if len(max_heap) < k: heapq.heappush(max_heap, -a[i]) # 大顶堆存负 else: # 当max_heap已经有k个元素,那么比较当前元素max_heap堆顶(负反)的大小 if a[i] < -max_heap[0]: # 将当前元素加入max_heap,同时弹出一个堆顶(最大元素)到min_heap num = -heapq.heappop(max_heap) heapq.heappush(max_heap, -a[i]) heapq.heappush(min_heap, num) else: heapq.heappush(min_heap, a[i]) - 然后处理操作操作1:添加x total += 1 if total < k: # 总元素个不足k,直接加入max_heap(因为查询时要求k个,现在不足,所以先加入,但查询时会判断不足k输出-1) heapq.heappush(max_heap, -x) else: # 如果当前max_heap的大小小于k(说明之前不足k,现在添加后可能达到k,也可能还是不足?因为total>=k不一定代表max_heap有k个?)—— 实际上,我们维护max_heap的大小为min(total, k)?这样比较麻烦。 # 我们换一种思路:我们始终保证max_heap的大小为min(total, k)?但是这样在添加元素时,如果total<k,那么max_heap的大小就是total;如果total>=k,那么max_heap的大小就是k。 # 所以,在添加元素时: if len(max_heap) < k: heapq.heappush(max_heap, -x) # 如果此时max_heap的大小等于k了,那么不需要调整,因为还没有超过k # 注意:当max_heap的大小达到k后,下一次添加元素就要开始调整了。 else: # 此时max_heap已经有k个元素,那么新来的元素x,需要max_heap的堆顶比较 if x < -max_heap[0]: # 将x加入max_heap,同时弹出堆顶到min_heap num = -heapq.heappop(max_heap) heapq.heappush(max_heap, -x) heapq.heappush(min_heap, num) else: heapq.heappush(min_heap, x) 操作2:查询 if total < k: print(-1) else: # 此时max_heap的大小应该是k(因为total>=k,我们维护了max_heap的大小为k) # 堆顶就是第k小的(注意:max_heap是存了负,所以负) print(-max_heap[0]) - 但是,上面的方法在添加元素时,如果max_heap的大小小于k,我们直接加入,这样max_heap的大小可能超过k吗?不会,因为我们用if len(max_heap)<k判断,所以不会超过k(在添加一个元素后,max_heap最多变成k,然后下一次添加就会进入else分支)。 然而,上述方法在初始构建堆时,如果n>k,我们只将前k个放入max_heap,剩下的放入min_heap。但是,这前k个不一定是排序后最小的k个?因为我们没有排序整个数组,而是逐个加入,并且用堆调整。实际上,我们构建max_heap时,每次加入一个元素,如果超过k,我们就弹出堆顶(最大元素)到min_heap,这样max_heap中始终是当前最小的k个(虽然堆顶是最大的,但其他元素不一定有序,但堆顶是第k小)。 但是,初始构建时,我们按照数组顺序加入,这样能保证得到最小的k个吗? 例如:数组[5,4,3,2,1],k=3 第一步:加入5 -> max_heap=[-5] (size=1) 第二步:加入4 -> max_heap=[-4,-5] (size=2) [注意:堆在内存中的存储顺序不一定有序,但堆顶是-4,即最大元素4] 第三步:加入3 -> max_heap=[-3,-5,-4] (size=3) 堆顶是-3(即3) 然后加入2:此时max_heap已经有3个,比较2堆顶3,2<3,所以弹出堆顶3(即-3)到min_heap,然后将2加入max_heap,此时max_heap=[-2,-3,-4](堆顶是2)?但是实际上,我们弹出3后,加入2,调整后堆顶应该是2吗? 重新调整:弹出堆顶(即-3),然后加入-2,然后调整:堆会变成[-2,-4,-3]?不对,堆调整后,堆顶应该是-2,然后左右孩子是-4-3?这样堆顶是2,所以第k小是2,正确(因为最小的3个是1,2,3,第3小是3?不对,最小的3个是1,2,3,第3小应该是3,但是我们的max_heap中现在存的是2,3,4?不对,我们弹出的是3,然后加入2,所以max_heap中现在是2,4,5?不对,我们初始max_heap中前三个是5,4,3,然后加入2时,我们弹出3(堆顶),然后加入2,所以max_heap变成[2,4,5]?但是堆调整后,堆顶应该是4?因为大顶堆,堆顶是最大元素。 我们需要重新审视:我们使用大顶堆来维护最小的k个,堆顶是这k个中的最大值,也就是第k小的。 初始数组[5,4,3,2,1]: 我们按顺序加入5,4,3:max_heap=[-5,-4,-3] -> 堆顶是-3(即3),然后加入22<3,所以弹出-3(即3)加入min_heap,然后将2加入max_heap:max_heap=[-5,-4,-2] -> 堆顶是-2(即2)?但是此时最小的三个是1,2,3,而我们的max_heap中现在是5,4,2?这显然不对。 问题出在哪里?因为我们没有将整个数组都加入堆中,而是只加入了前k个,然后剩下的元素我们按照与堆顶比较决定是否替换。但是,初始数组的前k个不一定是最小的k个,比如这个例子中,前三个是5,4,3,而最小的三个是1,2,3。所以我们需要将整个数组都按照添加元素的方式来处理。 修改初始化的方法: 初始化时,total = n 然后,我们遍历整个数组,对于每个元素: 如果当前max_heap的大小小于k,则直接加入max_heap(用负)。 否则,比较当前元素max_heap堆顶(负)的大小: 如果当前元素小于堆顶,则弹出堆顶,将当前元素加入max_heap。 否则,加入min_heap。 这样,初始化后,max_heap中保存了最小的k个(堆顶是第k小),min_heap保存其他较大的。 按照这个初始化方法,重新处理[5,4,3,2,1](k=3): 元素5:max_heap=[-5] -> size=1 元素4:size<3 -> max_heap=[-5,-4] (堆顶是-4,即4) 元素3:size<3 -> max_heap=[-5,-4,-3] (堆顶是-3,即3) 元素2:当前size=3,比较2堆顶3(2<3),所以弹出堆顶-3,加入min_heap(min_heap=[3]),然后将2加入max_heap:max_heap=[-5,-4,-2] -> 调整后,堆顶是-2(即2)?但是此时堆内元素是5,4,2,而最小的三个应该是1,2,3?这里我们还没有加入1。 元素1:当前max_heap大小=3,比较1堆顶2(1<2),所以弹出堆顶-2(即2)加入min_heap(min_heap=[2,3]),然后将1加入max_heap:max_heap=[-5,-4,-1] -> 堆顶是-1(即1)?此时max_heap=[5,4,1]?这显然不是最小的三个(最小的三个是1,2,3),但我们的max_heap中只有1,而23在min_heap中。 问题:我们初始化时,整个数组都是可用的,我们应该将整个数组都考虑进去,所以不能只用一个堆,而是需要两个堆一起调整。实际上,在初始化时,我们不应该将min_heapmax_heap分开构建,而是应该用上述方法:遍历整个数组,维护一个大小为k的大顶堆(存储最小的k个),然后剩下的放入min_heap。但是,上述过程并没有得到最小的k个。 为什么?因为我们在加入2的时候,弹出了3(当时堆顶),然后加入2,此时堆中为[5,4,2];然后加入1,又弹出了2(堆顶),加入1,堆中为[5,4,1]?这样堆顶是1,那么第k小就是1?但实际上第3小应该是3(因为数组排序后是1,2,3,4,5,第3小是3)?不对,题目中第k小:1,2,2,3,4,6中第3小是2(第二个2),所以第k小就是排序后第k个位置上的(从1开始)。所以对于[1,2,3,4,5]的第3小是3。 所以我们的方法得到的堆顶是1,而正确答案是3,显然错误。 错误原因:我们的大顶堆中应该存储最小的k个,但是在这个例子中,最小的3个是1,2,3,所以堆顶应该是3(因为3是这三个中最大的)。但是我们的方法最后max_heap中是1,4,5,堆顶是1(因为1最小,所以在大顶堆中它应该在堆底?不对,大顶堆的堆顶是最大值,所以1,4,5的大顶堆的堆顶是5?不对,我们加入1时,堆调整后应该是[5,4,1]?但堆的存储结构是:5在堆顶,然后左右孩子是41,所以堆顶是5(最大),那么第k小应该是5?更不对。 我们重新思考:我们的大顶堆存储最小的k个,那么堆顶应该是这k个中最大的,也就是第k小的。所以对于[1,2,3,4,5](k=3),我们应该让堆顶为3。那么堆中应该是[1,2,3](堆顶是3)?但是大顶堆的堆顶是最大值,所以堆顶应该是3,左右孩子是1221)都是可以的。 如何构建这样的大顶堆? 我们可以将前k个元素放入大顶堆,然后对于后面的每个元素,如果当前元素小于堆顶,那么堆顶就不可能是最小的k个之一(因为堆顶是当前k个中最大的,而当前元素比堆顶小,所以当前元素应该被包含在最小的k个中,而堆顶应该被移除)。所以,我们弹出堆顶,加入当前元素,然后调整堆。这样,遍历完整个数组,大顶堆中就是最小的k个,堆顶就是第k小。 所以正确的初始化: max_heap = [] for i in range(n): if i < k: heapq.heappush(max_heap, -a[i]) # 用负,这样堆顶是负的最小值(即原的最大值)的相反 else: if a[i] < -max_heap[0]: heapq.heappop(max_heap) heapq.heappush(max_heap, -a[i]) # 注意:这样构建后,max_heap中就是最小的k个(堆顶是第k小,负后) # 但是,我们并没有把剩下的元素放入min_heap?实际上,在后续操作中,我们可能需要min_heap吗? 在后续的添加操作中,我们同样需要维护两个堆吗? 在查询操作中,我们只需要max_heap的堆顶(因为堆顶就是第k小)。但是,添加操作时,如果当前总元素个>=k,那么新元素加入时,我们可能会替换堆顶(如果新元素比堆顶小),然后堆顶改变。但是,如果新元素比堆顶大,那么它不会影响当前的堆顶(因为第k小仍然是原来的堆顶)。但是,如果新元素比堆顶大,我们把它放在哪里?我们并不需要保存它吗?不,因为后续添加的元素可能会使得总元素增加,但k不变,那么第k小可能会变大(如果新元素很小,就会替换掉堆顶,堆顶变大?不对,堆顶是第k小,如果新元素很小,那么它就会加入堆,然后弹出堆顶(原来第k小),新的堆顶是第k+1小?不对,k是固定的,我们始终找第k小。 但是,如果新元素比堆顶大,那么它不会影响当前的第k小(因为当前第k小是堆顶,而新元素比堆顶大,所以它排在第k+1更后面,因此第k小不变)。所以我们可以不保存它?不行,因为后续添加的元素可能很小,会替换掉堆顶,但堆顶被替换后,我们还需要知道剩下的元素中哪些是候选的?所以我们需要保存所有元素吗?题目要求动态添加,总元素个可能达到n+m(40万),我们只需要第k小,所以不需要保存所有元素,只需要维护一个大小为k的大顶堆(存储最小的k个)即可。但是,如果新元素比堆顶大,那么它永远不可能成为前k小(直到被删除?但题目没有删除操作),所以我们可以不保存它?不行,因为k可能不变,但总元素增加,第k小可能会变化(比如k=3,当前堆顶是3,然后我们添加一个4,堆顶还是3;再添加一个2,那么2比3小,所以2要加入堆,然后堆中元素调整,弹出堆顶(当前堆中最大的那个,可能是3者4?不对,堆中保存最小的k个,现在总元素增加了,我们仍然需要最小的k个,所以堆的大小应该保持为k?那么当加入2时,堆的大小变成k+1,所以我们需要弹出堆顶(最大的那个),然后堆的大小恢复为k,堆顶就是新的第k小。 然而,我们之前初始化时,只维护了一个大小为k的堆,并没有保存其他元素。这样,当新元素比堆顶大时,我们直接丢弃?不行,因为后续添加的元素可能很小,需要堆顶比较,但堆顶可能被替换,而替换出去的堆顶元素我们还需要保存(因为可能后面又被替换回来?)所以,我们需要保存所有元素吗?题目没有删除操作,但是总元素可能很大(40万),而k最大20万,所以我们不能保存所有元素。 重新考虑:我们维护两个堆: 大顶堆max_heap:保存最小的k个,堆顶是这k个中的最大值(即第k小) 小顶堆min_heap:保存其他所有(比第k小大的) 这样,我们总元素个为total,max_heap的大小为min(total, k)。当total>=k时,max_heap的大小为k,min_heap的大小为total-k。 如何添加元素? 1. 如果当前总元素个total < k,那么直接将新元素加入max_heap(大顶堆,用负),然后调整max_heap(堆调整)。 2. 否则(total>=k): 将新元素x与max_heap堆顶(即第k小)比较: if x < 堆顶(即当前第k小): 说明x应该进入max_heap(因为它比第k小还小,所以它应该成为前k小之一,而原来的第k小就要被挤到第k+1小,所以需要将堆顶弹出到min_heap,然后将x加入max_heap。 操作:弹出max_heap堆顶(负),负后加入min_heap(小顶堆),然后将x加入max_heap(加入负)。 else: 将x加入min_heap。 这样,每次添加后,max_heap的大小在total>=k时始终为k(堆顶就是第k小),而min_heap保存其他较大的。 但是,在初始化时,我们需要将整个数组都加入,同样按照上述规则: total = n 初始化两个堆为空。 遍历数组a: if i < k: # 即当前max_heap的大小小于k,则加入max_heap heapq.heappush(max_heap, -a[i]) else: if a[i] < -max_heap[0]: # 弹出堆顶,加入min_heap top = -heapq.heappop(max_heap) heapq.heappush(min_heap, top) heapq.heappush(max_heap, -a[i]) else: heapq.heappush(min_heap, a[i]) 这样初始化后,max_heap中就是最小的k个(堆顶是第k小),min_heap中是其他。 然后处理操作操作1:添加x total += 1 if total <= k: # 注意,这里total<=k,因为一开始total=n(可能小于k,也可能大于等于k?),然后添加后total=n+1,所以这里用<=k表示还需要加入max_heap(因为max_heap的大小应该为min(total,k)) heapq.heappush(max_heap, -x) else: if x < -max_heap[0]: # 弹出堆顶到min_heap,然后加入x到max_heap top = -heapq.heappop(max_heap) heapq.heappush(min_heap, top) heapq.heappush(max_heap, -x) else: heapq.heappush(min_heap, x) 但是,这里有一个问题:当total<=k时,我们加入max_heap,但是当total==k+1时,我们进入else分支,然后如果x比堆顶小,我们就将堆顶弹出到min_heap,然后加入x。这样max_heap的大小保持为k(total>=k时,max_heap大小始终为k)。但是,如果一开始total=n>=k,那么初始化后max_heap的大小就是k,然后添加第一个元素时,total=n+1>=k+1>k,所以进入else分支。 然而,考虑初始时total=n<k,那么添加元素直到total=k,我们仍然没有建立好一个大小为k的max_heap(因为max_heap的大小等于total,而total<k时,max_heap的大小小于k)。当添加一个元素后,total=k,那么这次添加时,我们进入if total<=k分支,将x加入max_heap,此时max_heap的大小变为k(之前是k-1)。然后下一次添加(total=k+1)时,就进入else分支。 所以,这样维护是合理的。 操作2:查询 if total < k: print(-1) else: # 注意:此时max_heap的堆顶就是第k小(因为我们维护了max_heap的大小为min(total,k),当total>=k时,max_heap的大小为k,堆顶就是第k小) print(-max_heap[0]) 但是,这里有一个问题:在添加元素时,如果x比堆顶小,我们弹出了堆顶(原来第k小)到min_heap,然后加入x,那么新的堆顶就是新的第k小(比原来的小)。如果x比堆顶大,我们加入min_heap,那么第k小不变。 但是,我们并没有考虑min_heap中的元素可能比max_heap中的某些元素小?不会,因为我们每次添加元素时,如果x比当前第k小(即max_heap堆顶)小,那么我们就把它加入max_heap,同时把原来的堆顶(第k小)移到了min_heap。这样,min_heap中的所有元素都不小于max_heap的堆顶(即第k小)。因为: - 在初始化时,我们保证min_heap中的元素都>=max_heap堆顶(因为小于堆顶的都被放入max_heap了,而放入max_heap的同时会弹出一个更大的堆顶,所以min_heap中都是被弹出的较大的元素)。 - 在添加操作时,如果x>=堆顶,我们直接放入min_heap;如果x<堆顶,那么我们将堆顶(一个大于等于x的)放入min_heap,而x放入max_heap,这样min_heap中的元素仍然>=新的堆顶(因为新的堆顶是原来max_heap中第二大的?不一定,但是堆调整后,新的堆顶一定小于等于原来堆顶,而min_heap中原来的元素都>=原来堆顶,所以也>=新的堆顶)。 所以,min_heap中的元素都>=max_heap的堆顶(即第k小)。 因此,我们不需要担心min_heap中的元素会影响到第k小的值。 但是,我们还需要注意:在添加元素时,如果total>=k,我们只维护max_heap的大小为k,那么min_heap的大小就是total-k(可能很大),但是题目没有要求输出其他,所以没关系。 但是,我们考虑一种情况:当k=1时,max_heap中始终只有一个元素(最小的),然后每次添加时,如果新元素比当前最小的小,那么更新最小元素(将原来的最小弹出到min_heap,新元素加入max_heap);否则,新元素加入min_heap。这样,min_heap中保存了除最小元素以外的所有元素。 所以,代码实现如下: 注意:我们使用heapq,它默认是最小堆,所以max_heap用负存储。 步骤: 1. 读入n, m, k 2. 读入初始数组a 3. 初始化: max_heap = [] # 大顶堆,存负 min_heap = [] # 小顶堆,存正 total = n # 当前总元素个 4. 初始化构建: for i in range(n): if i < k: heapq.heappush(max_heap, -a[i]) else: if a[i] < -max_heap[0]: # 弹出堆顶,加入min_heap top = -heapq.heappop(max_heap) heapq.heappush(min_heap, top) heapq.heappush(max_heap, -a[i]) else: heapq.heappush(min_heap, a[i]) 5. 处理m个操作: for _ in range(m): op = input().split() if op[0] == '1': x = int(op[1]) total += 1 if total <= k: heapq.heappush(max_heap, -x) else: # 注意:当total>k时,我们进入else分支,但是一开始total可能等于k+1,所以这里用else if x < -max_heap[0]: top = -heapq.heappop(max_heap) heapq.heappush(min_heap, top) heapq.heappush(max_heap, -x) else: heapq.heappush(min_heap, x) else: # op[0]=='2' if total < k: print(-1) else: # 此时max_heap的大小为min(total, k) = k (因为total>=k) print(-max_heap[0]) 但是,我们测试一下示例: 输入:5 4 3 1 2 3 4 5 2 1 1 1 3 2 初始数组:a=[1,2,3,4,5], k=3, n=5, m=4 初始化: 遍历a[0]=1: i=0<3 -> max_heap=[-1] 遍历a[1]=2: i=1<3 -> max_heap=[-1,-2] -> 堆调整后,堆顶是-1(最小)?不对,堆调整后,堆顶应该是-2(因为堆是最小堆,存的是负,所以最小堆的堆顶是最小的负,即原最大的)?实际上,我们期望大顶堆,所以用负存入小顶堆,这样堆顶就是最小的负(即原最大的),所以堆顶就是当前堆中的最大值(原)的负。 但是,heapq是小顶堆,所以: max_heap = [] 加入-1: [ -1 ] -> 堆顶是-1 加入-2: 插入后,堆调整:堆顶是-2(因为-2<-1,所以堆顶是-2)?不对,应该是插入后,堆调整,堆顶是较小的那个,所以堆顶是-2?那么堆顶对应的原就是2,表示当前堆中最大值是2?但此时堆中只有12,最大值是2,所以正确。 加入a[2]=3: i=2<3 -> 加入-3 -> max_heap=[-3,-1,-2]?堆调整:小顶堆,堆顶是-3(最小),然后左右孩子是-1-2?不对,堆调整过程:插入-3,然后堆顶比较,因为当前堆顶是-2,而-3<-2,所以交换?所以堆变成:[-3, -1, -2] -> 堆顶是-3(原3)?但是此时堆中最大值是3,最小值是1(因为原负后,堆顶是最小的负,对应原最大的,所以堆顶的绝对值是原最大值,即3)。所以堆顶(-3)对应的原3就是当前堆中的最大值,即第3小(因为堆中只有3个,最小的3个,最大值就是第3小)。 然后加入a[3]=4: i=3>=k -> 比较4堆顶(-3的绝对值3): 4>3 -> 所以加入min_heap: min_heap=[4] 然后加入a[4]=5: 5>3 -> 加入min_heap: min_heap=[4,5] 然后开始操作操作1:2 -> 查询 此时total=5>=3,所以输出堆顶的绝对值:-max_heap[0]=3 -> 输出3 操作2:1 1 -> 添加1 total=6 1<3? 是,所以弹出堆顶-3(即3)到min_heap,然后加入-1到max_heap。 max_heap: 弹出-3后,加入-1 -> 调整:堆中原来还有-1-2(以及刚加入的-1?不对,原来堆中三个元素:-3,-1,-2,弹出-3后,堆中剩下[-1,-2](堆调整后:堆顶是-2?因为-2<-1)?然后加入-1:堆变成[-2,-1,-1] -> 堆顶是-2(原2)?所以新的堆顶是-2,即第3小变成2。 然后min_heap加入3:min_heap=[4,5,3](注意:min_heap是小顶堆,所以堆调整后,min_heap=[3,4,5]) 操作3:1 3 -> 添加3 total=7 3堆顶2比较:3>2,所以加入min_heap: min_heap=[3,3,4,5](调整后,堆顶是3) 操作4:2 -> 查询 输出堆顶的绝对值:2 所以输出:32,与示例一致。 但是,我们注意:在添加1后,max_heap中现在有:-1(原1),-2(原2),-1(原1)?不对,我们添加1的时候,堆中原本有[-3,-1,-2](三个元素),然后弹出-3(即3),然后加入-1,此时堆中为[-1,-2,-1](三个元素)。然后堆调整:小顶堆(存负),堆顶应该是这三个中最小的:即-2(因为-2<-1)?不对,-2-1比较,-2更小,所以堆顶是-2(对应原2),然后左右孩子是-1-1?所以堆顶是2,正确。 但是,堆中元素为:-2(原2),-1(原1),-1(原1)。这样,最小的三个是1,1,2,所以第3小是2,正确。 所以,代码实现如下: 注意:heapq的堆操作,heapq.heappush(heap, item) heapq.heappop(heap) 都是维护堆结构。 但是,我们不需要显式调整堆,因为heapq会自动调整。 但是,有一个问题:在初始化时,我们构建max_heap时,如果k>n,那么遍历完数组后,max_heap的大小为n(小于k),然后后续添加元素时,total<=k时,我们直接加入max_heap,直到total达到k。然后当total>k时,我们开始按照上述规则调整。 所以,这个方案可以处理所有情况。 但是,我们考虑性能:每次添加操作,最多进行两次堆操作(poppush),堆的大小最大为k(20万),所以每次操作的时间复杂度为O(logk),总操作为m(20万),所以总时间复杂度为O(m*logk),可以接受。 代码实现: 注意:我们使用两个堆,max_heapmin_heap。min_heap我们只用于存储比当前第k小大的,在添加操作中,如果新元素比第k小大,我们就加入min_heap。但是,在查询操作中,我们并不需要min_heap,所以min_heap中的元素可以不用管(除非后面添加的元素很小,导致从min_heap中元素?不对,我们并没有从min_heap中元素,因为添加的元素直接max_heap堆顶比较,而min_heap中的元素永远不会再进入max_heap(除非max_heap的堆顶变大,而我们的操作中,max_heap的堆顶只会变小(因为每次加入更小的元素,弹出较大的堆顶)?所以min_heap中的元素永远不会再被使用?) 实际上,min_heap中的元素在后续操作中,如果遇到添加的元素比当前堆顶小,我们可能会把堆顶弹出到min_heap,然后min_heap中的元素越来越多。但是,我们并不需要min_heap中的元素,所以我们可以不用min_heap?不对,我们添加的元素如果比当前堆顶大,我们把它加入min_heap,这样我们就保存了所有元素?但是题目没有要求输出其他元素,我们只需要第k小,所以min_heap中的元素我们不需要。但是,如果我们不保存,那么当添加的元素比当前堆顶小时,我们弹出堆顶(原第k小)后,这个弹出的元素我们就丢弃了?但是,如果后续k变小(但题目k不变),者有删除操作(题目没有),那么我们就需要用到这些元素。但是,题目中k是固定的,且没有删除操作,所以我们可以不保存比当前第k小大的?因为第k小只与最小的k个有关,而后续添加的元素如果比当前第k小大,那么它不会影响第k小(直到有k个比它小的元素出现,它才会被挤出最小的k个?不对,第k小是动态的,如果添加很多很小的,那么第k小会变小,而原来第k小可能变成第k+1小,所以我们应该保存所有元素?) 但是,我们上面的方法中,我们保存了所有元素:max_heap保存最小的k个,min_heap保存其他。这样,总元素个为total,我们保存了全部元素。但是,当添加的元素比当前堆顶小时,我们弹出堆顶(原第k小)到min_heap,然后加入新元素到max_heap,这样max_heap中还是k个元素(最小的k个),而min_heap中保存了其他元素。这样,当后续添加的元素比当前堆顶小时,我们继续调整。所以,我们保存了所有元素。 但是,题目没有要求保存所有元素,我们只需要查询第k小。所以,我们也可以不保存min_heap中的元素?不行,因为如果k变小(但题目k不变),者需要查询其他顺序统计量,我们就需要。但本题k固定,且只查询第k小。那么,我们是否可以只用一个大小为k的堆(大顶堆)来保存最小的k个,而其他元素丢弃?不行,因为丢弃后,如果后面添加的元素很小,而当前堆顶很大,那么我们需要用新元素替换堆顶,但是堆顶丢弃后,我们就无法知道剩下的元素中是否有更小的?所以,我们必须保存所有元素。 但是,本题中,我们保存了所有元素(在min_heapmax_heap中),所以总空间是O(n+m),最大40万,可以接受。 所以,我们按照上述方案实现。 但是,我们考虑:在添加操作中,如果x比堆顶小,那么我们将堆顶弹出到min_heap,然后加入x到max_heap。这样,min_heap中保存的是所有曾经被弹出max_heap的(以及那些一开始就比堆顶大的)。所以,min_heap中的元素都是大于等于当前堆顶(第k小)的。 因此,我们不需要担心元素丢失。 代码实现: 注意:在初始化时,我们遍历整个数组,然后构建两个堆。然后处理操作。 但是,在初始化时,我们可能遇到k=0?题目k>=1,所以不用考虑。 另外,注意堆操作的时间复杂度:每次堆操作O(log(size)),所以总时间O((n+m)logk),可以接受。 下面我们写代码: 注意:在Python中,heapq默认是小顶堆,所以min_heap我们可以直接使用heapq,而max_heap我们通过存储负来实现。 但是,min_heap我们也要用堆来维护吗?实际上,在后续操作中,我们只向min_heap添加元素,并不从中元素(除了在添加操作中,我们并不需要从min_heap中元素),所以我们可以不用堆来维护min_heap?但是,我们在添加操作中,只向min_heap添加元素,并不需要弹出元素,所以我们可以用一个列表来保存min_heap?但是,如果min_heap很大,我们添加元素时,直接append,时间复杂度O(1),而如果我们需要从min_heap中元素(比如调整到max_heap),我们并没有这样的操作。所以,我们实际上不需要min_heap为堆结构,只需要一个容器。但是,在添加操作中,我们只向min_heap添加,不删除,所以我们可以用一个列表。 但是,我们考虑:在添加元素时,如果x比堆顶小,我们需要将堆顶弹出,然后加入min_heap,然后加入x到max_heap。这个过程中,min_heap添加了一个元素(堆顶弹出的那个)。而其他情况(x>=堆顶)我们直接将x加入min_heap。所以,min_heap只需要支持添加操作,不需要弹出操作(除了在初始化时,我们也没有弹出操作)。所以,我们可以用一个列表,然后添加时用append。 但是,为什么我们之前用heapq来push到min_heap?因为如果min_heap是堆结构,那么当我们需要从中出最小值时(比如后续操作需要),我们可以快速出。但是,本题中,我们并不需要从min_heap中元素。所以,我们可以不用堆,而用列表。 但是,如果我们用列表,那么添加操作就是O(1),而用堆的push是O(log(size)),但是min_heap的大小可能很大(达到40万),所以用堆的push操作也是O(log(400000)),大约是20次操作,可以接受。而用列表append是O(1),但是如果我们不需要堆结构,那么用列表更高效。但是,我们这里用堆结构也没有问题,因为时间可以接受,而且如果后续需要扩展(比如支持删除),堆结构可能更好。 但是,题目没有删除操作,所以我们可以用列表。但是,我们之前已经用堆实现了,而且堆操作在添加时只多了一个log的因子,所以我们可以继续用堆。 为了代码一致性,我们还是用堆来维护min_heap(尽管可能没有必要)。 代码: 注意:我们使用两个堆,max_heap(小顶堆,存负min_heap(小顶堆,存正)。 但是,min_heap我们只用于存储,并不需要弹出,所以用列表也可以。但是,为了清晰,我们还是用堆。 但是,我们考虑空间:两个堆总共存储n+m个元素,最大40万,所以空间O(n+m),可以接受。 因此,我们使用heapq来操作两个堆。 但是,在min_heap中,我们只添加元素,没有弹出操作,所以它并不是一个严格意义上的堆(因为我们没有维护堆的性质,因为我们只添加,不弹出,所以堆的性质不会被破坏?不对,添加时我们调用了heapq.heappush,它会维护堆的性质。但是,我们并不需要从min_heap中弹出最小值,所以其实我们不需要维护堆性质。所以,我们可以用普通列表,然后添加元素用heapq.heappush?这样列表就是堆结构,但我们不需要弹出,所以也可以不用heapq,直接append。但是,为了代码简单(我们统一用heapq.push,这样如果以后需要扩展,比如支持删除,就可以用堆弹出),我们使用堆。 所以,最终代码: 注意:在初始化时,如果k>n,那么max_heap的大小为n,然后后续添加元素时,当total<=k时,我们直接加入max_heap,直到total=k。然后当total>k时,我们开始按照规则调整。 但是,在初始化时,如果k>n,那么min_heap是空的,然后添加元素时,如果total<=k,我们直接加入max_heap;当total>k时,我们开始调整:将新元素与max_heap堆顶比较,如果小,则弹出堆顶到min_heap,然后加入新元素;否则,加入min_heap。 所以,没有问题。 我们测试一下k>n的情况: 例如:n=2, k=3, m=2, 数组[1,2] 操作:添加3,然后查询。 初始化:max_heap=[-1,-2](大小2<3),min_heap=[] 添加3:total=3,满足total<=k(3<=3),所以加入max_heap:max_heap=[-1,-2,-3](堆顶是-1?不对,堆调整后,堆顶是-3(最小)?因为小顶堆,存负,所以堆顶是-3(原3)?但是此时堆中元素为1,2,3,堆顶是3(最大值),所以第3小应该是3?但是最小的三个就是1,2,3,第3小是3,正确。 查询:输出3。 所以,代码实现如下:</think>### 解决方案 #### 方法:使用双堆维护动态据流中的第K小元素 为了高效处理动态添加元素并查询第K小的,我们使用两个堆: - **大顶堆(`max_heap`)**:存储当前最小的K个(堆顶是这些中的最大值,即第K小的)。使用负存储以实现大顶堆效果。 - **小顶堆(`min_heap`)**:存储所有比大顶堆堆顶大的。 **操作逻辑**: 1. **初始化**: - 遍历初始数组,将前K个元素加入`max_heap`。 - 剩余元素若小于当前`max_heap`堆顶,则弹出堆顶加入`min_heap`,并将该元素加入`max_heap`;否则直接加入`min_heap`。 2. **添加元素(操作1)**: - 若总元素`total < K`,直接加入`max_heap`。 - 若`total >= K`: - 若新元素小于`max_heap`堆顶,则弹出堆顶加入`min_heap`,并将新元素加入`max_heap`。 - 否则,将新元素加入`min_heap`。 3. **查询(操作2)**: - 若`total < K`,输出`-1`。 - 否则,输出`max_heap`堆顶的绝对值(即第K小的)。 #### 代码实现 ```python import heapq import sys def main(): data = sys.stdin.read().split() if not data: return n = int(data[0]) m = int(data[1]) k = int(data[2]) a = list(map(int, data[3:3+n])) ops = [] index = 3 + n for i in range(m): if data[index] == '1': ops.append((1, int(data[index+1]))) index += 2 else: ops.append((2, 0)) index += 1 max_heap = [] # 大顶堆,存储负 min_heap = [] # 小顶堆,存储正 total = n # 初始化:构建大顶堆小顶堆 for num in a: if len(max_heap) < k: heapq.heappush(max_heap, -num) else: if num < -max_heap[0]: # 弹出堆顶并加入小顶堆,新元素加入大顶堆 top = -heapq.heappop(max_heap) heapq.heappush(min_heap, top) heapq.heappush(max_heap, -num) else: heapq.heappush(min_heap, num) # 处理操作 output = [] for op in ops: if op[0] == 1: # 添加元素 x = op[1] total += 1 if total <= k: heapq.heappush(max_heap, -x) else: if x < -max_heap[0]: top = -heapq.heappop(max_heap) heapq.heappush(min_heap, top) heapq.heappush(max_heap, -x) else: heapq.heappush(min_heap, x) else: # 查询 if total < k: output.append("-1") else: output.append(str(-max_heap[0])) # 输出结果 print("\n".join(output)) if __name__ == "__main__": main() ``` #### 代码解释 1. **初始化双堆**: - 遍历初始数组,将前K个元素加入`max_heap`(以负形式存储)。 - 剩余元素若小于当前`max_heap`堆顶(即能进入前K小),则替换堆顶并调整堆结构。 2. **处理操作**: - **添加元素(操作1)**:根据当前总元素`total`与K的关系,决定直接加入`max_heap`与堆顶比较后调整。 - **查询(操作2)**:若元素不足K个输出`-1`;否则输出`max_heap`堆顶的绝对值(第K小的)。 3. **输出**:收集所有查询结果并一次性输出。 此方法高效维护了动态据流的第K小元素,添加查询操作的时间复杂度均为O(log K),适合大规模据处理。 ### 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值