Как предлагает mcdowella в их ответе, пусть K2 = K/2, округляя вверх, и пусть M будет наименьшей степенью числа 2. то есть >= К2. Многообещающим подходом был бы поиск смежных блоков K2 нулей, полностью содержащихся в одном блоке размера M, и, как только мы их нашли, проверка соседних блоков размера M, чтобы увидеть, содержат ли они достаточное количество смежных нулей. Для начального сканирования, если количество нулей в блоке ‹ K2, очевидно, мы можем его пропустить, а если количество нулей >= K2 и размер блока >= 2*M, мы можем посмотреть на оба подблока.
Это предполагает следующий код. Ниже A[0 .. N-1] — массив дерева Фенвика; Предполагается, что N является степенью числа 2. Я предполагаю, что вы считаете пустые слоты, а не непустые; если вы предпочитаете считать пустые слоты, достаточно легко перейти от одного к другому.
initialize q as a stack data structure of triples of integers
push (N-1, N, A[n-1]) onto q
# An entry (i, j, z) represents the block [i-j+1 .. i] of length j, which
# contains z zeroes; we start with one block representing the whole array.
# We maintain the invariant that i always has at least as many trailing ones
# in its binary representation as j has trailing zeroes. (**)
initialize r as an empty list of pairs of integers
while q is not empty:
pop an entry (i,j,z) off q
if z < K2:
next
if FW(i) >= K:
first_half := i - j/2
# change this if you want to count nonempty slots:
first_half_zeroes := A[first_half]
# Because of invariant (**) above, first_half always has exactly
# the right number of trailing 1 bits in its binary representation
# that A[first_half] counts elements of the interval
# [i-j+1 .. first_half].
push (i, j/2, z - first_half_zeroes) onto q
push (first_half, j/2, first_half_zeroes) onto q
else:
process_block(i, j, z)
Это позволяет нам обрабатывать по порядку все блоки размера M, содержащие не менее K/2 нулей. Вы даже можете рандомизировать порядок, в котором вы вставляете первую и вторую половину в q, чтобы получить блоки в случайном порядке, что может быть полезно для борьбы с ситуацией, когда первая половина вашего массива заполняется гораздо быстрее, чем последняя половина.
Теперь нам нужно обсудить, как обрабатывать отдельный блок. Если z = j, то блок полностью заполнен нулями, и мы можем смотреть как влево, так и вправо, чтобы добавить нули. В противном случае нам нужно выяснить, начинается ли он с >= K/2 смежных нулей, и если да, то с какого именно, а затем проверить, заканчивается ли предыдущий блок подходящим количеством нулей. Точно так же мы проверяем, заканчивается ли блок >= K/2 непрерывных нулей, и если да, то сколько именно, а затем проверяем, начинается ли следующий блок с подходящего количества нулей. Поэтому нам понадобится процедура для определения количества нулей, с которых начинается или заканчивается блок, возможно, с сокращением, если это не меньше a или не больше b. Чтобы быть точным: пусть end_with_zeroes(i, j, min, max) будет процедурой, которая возвращает количество нулей, которыми заканчивается блок из [i-j+1 .. j], с ярлыком для возврата max, если результат будет больше max и min, если результат будет меньше min. Аналогично для start_with_zeroes(i, j, min, max).
def process_block(i, j, z):
if j == z:
if i > j:
a := ends_with_zeroes(i-j, j, 0, K-z)
else:
a := 0
if i < N-1:
b := starts_with_zeroes(i+j, j, K-z-a-1, K-z-a)
else:
b := 0
if b >= K-z-a:
print "Found: starting at ", i - j - a + 1
return
# If the block doesn't start or end with K2 zeroes but overlaps with a
# correct solution anyway, we don't need to find it here -- we'll find it
# starting from the adjacent block.
a := starts_with_zeroes(i, j, K2-1, j)
if i > j and a >= K2:
b := ends_with_zeroes(i-j, j, K-a-1, K-a)
if b >= K-a:
print "Found: starting at ", i - j - a + 1
# Since z < 2*K2, and j != z, we know this block doesn't end with K2
# zeroes, so we can safely return.
return
a := ends_with_zeroes(i, j, K2-1, j)
if i < N-1 and a >= K2:
b := starts_with_zeroes(i+j, K-a-1, K-a)
if b >= K-a:
print "Found: starting at ", i - a + 1
Обратите внимание, что во втором случае, когда мы находим решение, можно сдвинуть начальную точку немного дальше влево. Вы можете проверить это отдельно, если вам нужна самая первая позиция, с которой она может начаться.
Теперь все, что осталось, это реализовать start_with_zeroes и end_with_zeroes. Чтобы убедиться, что блок начинается как минимум с min нулей, мы можем проверить, что он начинается с 2 ^ h нулей (где 2 ^ h ‹ = min), проверив соответствующую запись Фенвика; затем аналогичным образом проверьте, начинается ли он с 2 ^ H нулей, где 2 ^ H >= max, чтобы сократить путь в другую сторону (за исключением случаев, когда max = j, сложнее найти правильный счет из дерева Фенвика); затем найдите точное число.
def starts_with_zeroes(i, j, min, max):
start := i-j
h2 := 1
while h2 * 2 <= min:
h2 := h2 * 2
if A[start + h2] < h2:
return min
# Now h2 = 2^h in the text.
# If you insist, you can do the above operation faster with bit twiddling
# to get the 2log of min (in which case, for more info google it).
while h2 < max and A[start + 2*h2] == 2*h2:
h2 := 2*h2
if h2 == j:
# Walk up the Fenwick tree to determine the exact number of zeroes
# in interval [start+1 .. i]. (Not implemented, but easy.) Let this
# number be z.
if z < j:
h2 := h2 / 2
if h2 >= max:
return max
# Now we know that [start+1 .. start+h2] is all zeroes, but somewhere in
# [start+h2+1 .. start+2*h2] there is a one.
# Maintain invariant: the interval [start+1 .. start+h2] is all zeroes,
# and there is a one in [start+h2+1 .. start+h2+step].
step := h2;
while step > 1:
step := step / 2
if A[start + h2 + step] == step:
h2 := h2 + step
return h2
Как видите, start_with_zeroes довольно восходящий. Я думаю, что для end_with_zeroes вам следует использовать более нисходящий подход, поскольку изучение второй половины чего-либо в дереве Фенвика немного сложнее. Вы должны быть в состоянии сделать аналогичный тип итерации в стиле бинарного поиска.
Этот алгоритм определенно не O(log(N)), и у меня есть подозрение, что это неизбежно. Дерево Фенвика просто не дает информации, подходящей для вашего вопроса. Однако я думаю, что этот алгоритм будет работать достаточно хорошо на практике, если подходящие интервалы достаточно распространены.
person
Erik P.
schedule
19.11.2013