Поиск подпоследовательности максимальной длины, сумма которой отрицательна и нечетна
Условие:
Пусть S — последовательность из N целых чисел, пронумерованных подряд начиная с 1. Обозначим S(L, R) подпоследовательность, состоящую из идущих подряд элементов, входящих в S, начиная с элемента с номером L и заканчивая элементом с номером R. Требуется найти такие значения номеров элементов L и R, где 0 < L< R ≤ N, чтобы сумма элементов подпоследовательности S(L, R) была нечётной и отрицательной.
В ответе укажите длину подобной подпоследовательности (то есть количество элементов, входящих в эту подпоследовательность). Если таких подпоследовательностей несколько — укажите максимальную длину из них.
Входные данные
Дано два входных файла (файл А(~1000 чисел) и файл В(~300 000 чисел)), каждый из которых в первой строке содержит число N (3 < N < 10 000 000) - количество целых чисел. Каждая из следующих М строк содержит одно целое число, значение которого по модулю не превышает 1000.
В ответе укажите два числа: сначала значение искомой величины для файла А, затем — для файла В.
Типовой пример организации данных во входном файле
8
-73
153
35
0
-44
78
-69
2
При таких входных данных условию задачи удовлетворяет сумма 0 + (-44) + 78 + (-69) и 0 + (-44) + 78 + (-69) + 2. Ответом на вопрос задачи является число 5.
К задаче есть 2 файла: на и на 300 000 чисел.
Моё решение:
Для первого файла подходит переборный алгоритм О(N^2). Для второго он не подходил, поэтому я попытался создать другой.
Идея была в том, чтобы создать массив сумм всех элементов от первого до i-того элемента и 2 массива предыдущих сумм для чётных и нечётных значений, каждая из которой была бы больше предыдущей. А потом проверять разность элементов первого массива и другого с иной чётностью. Если эта разность меньше 0, то переходить для следующей суммы.
Проблема в том, что после 30 000 элемента скорость сильно замедляется и выполнение для второго файла занимает несколько часов. Может ли кто-нибудь подсказать более оптимальный метод решения?
Мой код
f = open('file.txt')
n = int(f.readline())
a = [int(f.readline())]
sums = [a[0]]
for i in f:
i = int(i)
a.append(i)
sums.append(sums[-1]+i)
mxs0 = [[], []]
mxs1 = [[], []]
k0 = []
k1 = []
for i in range(2,n):
if sums[i-2] % 2 == 0:
if len(k0) == 0:
k0.append(i-2)
elif k0[-1] < sums[i-2]:
k0.append(i-2)
if sums[i-2] % 2 == 1:
if len(k1) == 0:
k1.append(i-2)
elif k1[-1] < sums[i-2]:
k1.append(i-2)
mxs0.append(k0)
mxs1.append(k1)
lens = []
for i in range(2,n):
print(i)
if sums[i]%2==0:
if len(mxs1[i]) > 0 > sums[i]-sums[mxs1[i][-1]]:
for j in range(len(mxs1[i])):
if sums[i]-sums[mxs1[i][j]] < 0:
lens.append(a[mxs1[i][j] + 1:i + 1])
break
else:
if sums[i] < 0:
lens.append(a[0:i+1])
else:
if len(mxs0[i]) > 0 > sums[i]-sums[mxs0[i][-1]]:
for j in range(len(mxs0[i])):
if sums[i]-sums[mxs0[i][j]] < 0:
lens.append(a[mxs0[i][j] + 1:i + 1])
break
cc = 0
lens = [i for i in lens if type(i) != int]
for i in range(len(lens)):
cc = max(cc, len(lens[i]))
print(cc)
Ответы (1 шт):
Вы отлично ухватили основную идею решения - возрастающие списки четных и нечетных кумулятивных сумм. Не хватило чуть-чуть - вместо линейного поиска по этим спискам использовать бинарный поиск, это позволяет вместо квадратичной сложности (в худшем случае) получить O(nlogn). Файлов у меня нет, поэтому сгенерировал случайные списки (плохие случаи при этом трудно получить), и сверил для небольших размеров с примитивной функцией (dumb
). На длине 300000 maxlenoddneg
работает менее секунды.
import random, bisect
a = [random.randint(-100000,100000) for _ in range(300000)]
#print(a)
def dumb(a):
n = len(a)
mi = 0
mj = 0
maxlen = 0
for i in range(n-1):
for j in range(i+1, n):
s = sum(a[i:j+1])
if s%2 and s < 0:
if j-i > maxlen:
maxlen = j-i
mi = i
mj = j
return maxlen+1 if maxlen > 0 else 0, mi, mj
def maxlenoddneg(a):
n = len(a)
csum = [0]*(n+1)
for i in range(n):
csum[i+1] = csum[i] + a[i]
#print(csum)
sums = [[] for _ in range(2)]
indsums = [[] for _ in range(2)]
maxlen = 0
for i in range(n+1):
oddeven = csum[i] % 2
if len(sums[1-oddeven]):
k = bisect.bisect_left(sums[1-oddeven], csum[i])
if k < len(indsums[1-oddeven]):
j = indsums[1-oddeven][k]
if i-j > maxlen:
maxlen = i - j
#print(oddeven, csum[i], j, i)
if (len(sums[oddeven])==0) or (csum[i]>sums[oddeven][-1]):
sums[oddeven].append(csum[i])
indsums[oddeven].append(i)
#print(sums)
print()
#print(sums)
#print(indsums)
return maxlen
print(maxlenoddneg(a))
#print(dumb(a))