📅  最后修改于: 2023-12-03 15:28:45.554000             🧑  作者: Mango
这一题考查的是程序员的数组操作能力和对算法的理解。
给定一个由正整数构成的序列,你需要在这个序列中找到一个非空的连续子序列,使得这个子序列中所有数的和能够整除序列长度。也就是,找到一个起始下标和终止下标,使得它们之间的数字和能够整除终止下标减去起始下标加上 1。
如果有多个满足条件的子序列,返回其中最短的一个。如果仍然存在多个,返回其中起始下标最小的那个。
输入的第一行包含一个整数 $n$,表示序列的长度。
接下来一行包含 $n$ 个正整数 $a_1,a_2,\ldots,a_n$,表示序列中的元素。
输出一行两个整数 $l$ 和 $r$,表示所找到的最短的满足条件的子序列的起始下标和终止下标。如果不存在这样的子序列,则输出 -1。
6
1 7 2 6 3 4
1 4
4
4 4 4 4
0 3
为了简化问题,我们可以先对输入的序列求一次前缀和。这样我们就可以方便地求出两个下标之间元素的和。
将所有前缀和对序列长度取余的结果放到一个数组中。注意到,如果两个下标之间元素的和能够整除序列长度,那么它们对应的前缀和对序列长度取余的结果一定是相等的。因此,我们只需要在这个数组中找出相等的两个元素,它们之间的元素的和就是一个满足条件的子序列。
在找这样一组下标的时候,我们要选择跨度最小的下标对。也就是说,对于任意满足条件的下标对 $(i,j)$ 和 $(k,l)$($i \leq k < j \leq l$),若 $j-i > l-k$,我们应该选择 $(k,l)$ 作为满足条件的下标对。
注意到序列中的元素均为正整数,因此前缀和对序列长度取余的结果必定在 $[0,n)$ 范围内。假设我们的序列长度为 $n$,那么给定两个余数 $r_1$ 和 $r_2$,它们之间的跨度应当不超过 $n$。
这启示我们可以使用一个桶,将余数相同的前缀和存放在同一个桶中。这样,在同一个桶中任意两个前缀和的跨度都是不超过 $n$ 的。于是,我们可以遍历所有桶中出现的前缀和,找到跨度最小的一组下标对。
下面是对应的 Python 代码实现:
n = int(input())
a = list(map(int, input().split()))
sums = [0] * (n + 1)
for i in range(n):
sums[i + 1] = sums[i] + a[i]
buckets = [[] for i in range(n)]
for i, s in enumerate(sums):
buckets[s % n].append(i)
min_span = n
min_span_start = None
for bucket in buckets:
if len(bucket) <= 1:
continue
for i in range(len(bucket)):
for j in range(i + 1, len(bucket)):
span = bucket[j] - bucket[i]
if span > min_span:
break
if span < min_span or bucket[i] < min_span_start:
min_span = span
min_span_start = bucket[i]
if min_span_start is None:
print(-1)
else:
print(min_span_start, min_span_start + min_span - 1)
程序首先读入序列的长度 $n$ 和 $n$ 个正整数 $a_1,\ldots,a_n$。然后,它计算出序列的前缀和,并将其对序列长度取余的结果存放在桶中。
程序遍历所有桶中出现的前缀和,找到跨度最小的一组下标对。如果不存在这样的下标对,则程序输出 -1。
注意到,在实现中,我们特判了那些只有一个前缀和的桶。由于题目说明要求返回一个非空子序列,因此只有在一个桶中出现了至少两个前缀和时才有可能返回正确的结果。如果一个桶中只有一个元素,我们就不考虑它是否能够作为起点,直接跳过。这样可以避免一些不必要的判断。