办公小浣熊
Raccoon - AI 智能助手

个人知识库的搜索功能如何优化?

在信息爆炸的时代,我们每个人的电脑或云端都散落着无数文档、笔记、邮件和图片,它们共同构成了我们的个人知识库。这本应是我们最强大的外脑,但当我们需要快速从中#问题说明
#给定一个包含N个整数的序列:A = (a_1, a_2, …, a_N)和一个整数K。
#请问有多少个A的连续子数组满足子数组的和至少为K?

#约束条件
#1 ≦ N ≦ 2× 10^5
#1 ≦ K ≦ 10^{18}
#1 ≦ a_i ≦ 10^9

#输入的所有数值均为整数。

#输入
#输入从标准输入中按以下格式给出:
#N K
#a_1 a_2 … a_N

#输出
#输出答案。

#输入样例 1
#4 10
#6 1 2 7

#输出样例 1
#2
#样例1解释
#以下两个子数组满足条件:
#A[1..4] = (6,1,2,7),和为16
#A[2..4] = (1,2,7),和为10

#输入样例 2
#3 5
#3 3 3

#输出样例 2
#3
#样例2解释
#以下三个子数组满足条件:
#A[1..2] = (3,3),和为6
#A[1..3] = (3,3,3),和为9
#A[2..3] = (3,3),和为6

def main():

import sys  
import bisect  

data = sys.stdin.read().split()  
n = int(data[0])  
K = int(data[1])  
a = list(map(int, data[2:2+n]))  

# 计算前缀和  
prefix_sums = [0] * (n + 1)  
for i in range(1, n+1):  
    prefix_sums[i] = prefix_sums[i-1] + a[i-1]  

# 问题转化为:对于每个右端点j,找到最左的i,使得prefix_sums[j] - prefix_sums[i] >= K  
# 即 prefix_sums[i] <= prefix_sums[j] - K  
# 注意:i的范围是0到j-1  

# 但是直接枚举j,然后二分查找i?  
# 我们需要的是对于每个j,找到满足prefix_sums[i] <= prefix_sums[j]-K的i的个数(i从0到j-1)  
# 注意前缀和数组可能不是单调的?实际上前缀和是单调递增的,因为a_i都是正数。  

# 但是注意:我们的前缀和数组prefix_sums是单调递增的,所以我们可以维护一个有序序列(实际上就是前缀和数组本身),然后对于每个j,在prefix_sums[0:j]中二分查找有多少个i满足prefix_sums[i] <= prefix_sums[j]-K。  

# 但是如果我们直接对每个j在prefix_sums[0:j]中二分,那么总复杂度是O(n log n),因为n最大20万,所以可行。  

# 但是注意:K可能非常大,以至于对于某些j,prefix_sums[j]-K可能为负数,而我们的前缀和都是非负的(因为a_i为正),所以如果prefix_sums[j]-K<0,那么满足条件的i只能从0开始?实际上,如果prefix_sums[j]-K<0,那么只有prefix_sums[i]<=负数,而我们的前缀和最小为0(因为prefix_sums[0]=0,且后续递增),所以这种情况下没有i满足条件?不对,因为prefix_sums[i]>=0,所以如果prefix_sums[j]-K为负数,那么没有i满足条件。  

# 另一种思路:使用双指针?因为前缀和是递增的,所以也可以使用双指针?但这里我们要求的是对于每个j,找到最小的i0,使得从i0到j-1都满足条件?实际上,如果固定j,我们需要统计i从0到j-1中满足prefix_sums[i] <= prefix_sums[j]-K的i的个数。由于前缀和数组是递增的,我们可以对前缀和数组进行二分查找。  

count = 0  
# 我们需要一个有序的列表来存储前缀和?实际上前缀和数组本身已经有序(因为a_i正数),所以我们可以直接使用前缀和数组的前j个元素作为有序列表?但是注意,前缀和数组是递增的,所以我们可以直接使用二分查找。  

# 但是注意:我们需要在prefix_sums[0:j]中查找有多少个值<=target(target=prefix_sums[j]-K)。  
# 我们可以用bisect_right来找到第一个大于target的位置,这个位置就是满足条件的个数。  

# 但是注意:前缀和数组是单调递增的,所以我们可以这样做。  

# 但是注意:j从1到n,我们每次需要在前缀和的前j个元素(即prefix_sums[0]到prefix_sums[j-1])中查找。  
# 因为子数组是从i+1到j,所以i的范围是0到j-1。  

# 但是注意:如果我们直接对每个j进行二分,那么我们需要在prefix_sums[0:j]中二分,这个列表长度是j,总复杂度是O(n log n),可以接受。  

for j in range(1, n+1):  
    target = prefix_sums[j] - K  
    if target < 0:  
        # 没有i满足条件,因为prefix_sums[i]>=0  
        continue  
    # 在prefix_sums[0:j]中查找第一个大于target的位置  
    pos = bisect.bisect_right(prefix_sums, target, 0, j)  # 在[0, j)区间内查找  
    count += pos  # 因为索引从0到pos-1的值都<=target,所以个数为pos  

print(count)  

if name == “main”:

main()  

但是注意:样例输入1:N=4, K=10, a=[6,1,2,7]

前缀和: [0, 6, 7, 9, 16]

j=1: target=6-10=-4 <0 -> 跳过

j=2: target=7-10=-3 <0 -> 跳过

j=3: target=9-10=-1 <0 -> 跳过

j=4: target=16-10=6

在prefix_sums[0:4]即[0,6,7,9]中查找<=6的个数:0,6 -> 2个(索引0和1)

所以count=2,正确。

样例输入2:N=3, K=5, a=[3,3,3]

前缀和: [0,3,6,9]

j=1: target=3-5=-2 <0 -> 跳过

j=2: target=6-5=1 -> 在[0,3]中查找<=1的个数:只有0(因为3>1)?不对,前缀和[0,3]:0<=1,但是3>1,所以只有1个?但是预期是3个。

实际上,对于j=2:子数组[3,3]的和是6>=5,所以应该被计数。但是这里我们只统计到一个?为什么?因为i可以是0?但是i=0时,子数组是a[1..2]确实满足。但是i=1呢?i=1对应子数组a[2..2]?不对,注意我们的子数组是从i+1到j。当j=2时,i的范围是0和1?但是i=1对应的子数组是a[2..2](即第二个数),这个子数组的和是3,小于5,所以不应该被计数。所以j=2时只有i=0满足条件,所以计数1。

j=3: target=9-5=4 -> 在prefix_sums[0:3]即[0,3,6]中查找<=4的个数:0和3,所以2个?但是预期总共3个。

那么我们现在只得到1+2=3,符合预期。

但是为什么j=3时,i=0和i=1满足?

i=0: 子数组a[1..3]:3+3+3=9>=5

i=1: 子数组a[2..3]:3+3=6>=5

i=2: 子数组a[3..3]:3,不满足。所以确实两个。

所以j=2时一个,j=3时两个,总共三个,正确。

因此代码正确。

小浣熊家族 Raccoon - AI 智能助手 - 商汤科技

办公小浣熊是商汤科技推出的AI办公助手,办公小浣熊2.0版本全新升级

代码小浣熊办公小浣熊