Подсчет по запросу

Для массива из N положительных элементов. Предположим, что мы перечислим все N × (N + 1)/2 непустые непрерывные подмассивы массива A, а затем заменили все подмассивы на максимальный элемент, присутствующий в соответствующем подмассиве. Итак, теперь мы имеем N × (N + 1)/2 элементов, где каждый элемент является максимальным среди своего подмассива.

Теперь у нас есть Q-запросы, где каждый запрос является одним из трех типов:

1 K: Нам нужно рассчитывать числа, строго превышающие K среди тех элементов N × (N + 1)/2.

2 K: Нам нужно считать числа, строго меньшие K среди тех элементов N × (N + 1)/2.

3 K: Нам нужно подсчитать числа, равные K среди тех элементов N × (N + 1)/2.

Теперь главная проблема, стоящая перед лицом N, может быть до 10 ^ 6. Поэтому я не могу сгенерировать все эти элементы N × (N + 1)/2. Помогите решить эту проблему.

Пример: пусть N = 3 и Q = 2. Пусть массив A будет [1,2,3], тогда все под массивы:

[1] -> [1]
[2] -> [2]
[3] -> [3]
[1,2] -> [2]
[2,3] -> [3]
[1,2,3] -> [3]

Итак, теперь мы имеем [1,2,3,2,3,3]. При Q = 2 так:

Query 1 : 3 3

Это означает, что нам нужно указать количество чисел, равное 3. Таким образом, ответ равен 3, поскольку в сгенерированном массиве есть 3 числа, равные 3.

Query 2 : 1 4

Это означает, что нам нужно рассчитать количество чисел больше 4. Таким образом, ответ равен 0, поскольку в генерируемом массиве никто больше 4.

Теперь и N, и Q могут быть до 10 ^ 6. Итак, как решить эту проблему. Какая структура данных должна быть подходящей для ее решения.

Ответ 1

Я считаю, что у меня есть решение в O(N + Q*log N) (Подробнее о временная сложность). Хитрость заключается в том, чтобы сделать большую подготовку с вашим массивом до того, как поступит даже первый запрос.

  • Для каждого номера укажите, где находится первое число слева и справа от этого числа, которое строго больше.

Пример: для массива: 1, 8, 2, 3, 3, 5, 1 как 3 левый блок будет состоять из 8, правый блок будет состоять из 5.

Это можно определить в линейном времени. Вот как: Храните стек предыдущих максимумов в стеке. Если появляется новый максимум, удалите максимумы из стека, пока не дойдете до элемента, большего или равного текущему. Иллюстрация:

Иллюстрация

В этом примере в стеке: [15, 13, 11, 10, 7, 3] (вы, конечно, будете держать индексы, а не значения, я просто использую значение для лучшей читаемости).

Теперь мы читаем 8, 8 >= 3, поэтому удаляем 3 из стека и повторяем. 8 >= 7, удалите 7. 8 < 10, поэтому мы прекращаем удаление. Мы устанавливаем 10 как 8 левый блок и добавляем 8 к стеку максимумов.

Кроме того, всякий раз, когда вы удаляете из стека (3 и 7 в этом примере), установите правильный блок удаленного номера на текущий номер. Одна проблема: правый блок будет установлен на следующее число больше или равно, а не строго больше. Вы можете исправить это, просто проверив и переведя нужные блоки.

  1. Вычислить, какое число - сколько раз максимум некоторой подпоследовательности.

Так как для каждого номера теперь вы знаете, где находится следующее большее число слева/справа, я надеюсь, что вы найдете подходящую математическую формулу для этого.

Затем сохраните результаты в hashmap, ключ будет значением числа, а значение будет состоять в том, сколько раз это число является максимальным для некоторой подпоследовательности. Например, запись [4->12] означает, что число 4 является максимальным в 12 подпоследовательностями.

Наконец, извлеките все пары ключ-значение из хэш-карты в массив и отсортируйте этот массив с помощью ключей. Наконец, создайте префикс sum для значений этого отсортированного массива.

  1. Обработать запрос

Для запроса "точно k", просто бинарный поиск в вашем массиве, для more/less than k``, двоичный поиск ключа k, а затем используйте префиксный массив.

Ответ 2

Этот ответ является адаптацией этого другого ответа, который я написал ранее. Первая часть точно такая же, но другие специфичны для этого вопроса.

Здесь реализована версия O (n log n + q log n), использующая упрощенную версию дерева сегментов.

Создание дерева сегментов: O (n)

На практике то, что он делает, это взять массив, скажем:

A = [5,1,7,2,3,7,3,1]

И построим дерево с поддержкой массива, которое выглядит так:

Дерево сегментов

В дереве первое число - это значение, а второе - индекс, где он появляется в массиве. Каждый node является максимальным из двух его детей. Это дерево поддерживается массивом (очень похожим на кучное дерево), где дети индекса i находятся в индексах i*2+1 и i*2+2.

Затем для каждого элемента легко найти ближайшие более крупные элементы (до и после каждого элемента).

Чтобы найти ближайший большой элемент слева, мы идем вверх в дереве, ищем первый родительский элемент, где левое node имеет значение больше, а индекс меньше аргумента. Ответ должен быть дочерним элементом этого родителя, затем мы спускаемся в дерево, ища самый правый node, который удовлетворяет одному и тому же условию.

Аналогично, чтобы найти ближайший большой элемент справа, мы делаем то же самое, но ищем правый node с индексом, большим, чем аргумент. И при спуске мы ищем самый левый node, который удовлетворяет условию.

Создание совокупного частотного массива: O (n log n)

Из этой структуры мы можем вычислить частотный массив, который показывает, сколько раз каждый элемент отображается как максимальный в списке субарах. Нам просто нужно подсчитать, сколько меньших элементов находится слева и справа от каждого элемента и умножить эти значения. Для массива example ([1, 2, 3]) это будет:

[(1, 1), (2, 2), (3, 3)]

Это означает, что 1 появляется только один раз как максимум, 2 появляется дважды и т.д.

Но нам нужно ответить на запросы диапазона, поэтому лучше иметь кумулятивную версию этого массива, которая будет выглядеть так:

[(1, 1), (2, 3), (3, 6)]

(3, 6) означает, например, что существует 6 подмассивов с максимумами, меньшими или равными 3.

Ответ на q: O (q log n)

Затем, чтобы ответить на каждый запрос, вам просто нужно сделать двоичный поиск, чтобы найти нужное значение. Например. Если вам нужно найти точное число 3, вы можете захотеть сделать: query(F, 3) - query(F, 2). Если вы хотите найти тех, кто меньше 3, вы делаете: query(F, 2). Если вы хотите найти те, которые больше 3: query(F, float('inf')) - query(F, 3).

Реализация

Я реализовал его на Python и, похоже, хорошо работает.

import sys, random, bisect
from collections import defaultdict
from math import log, ceil

def make_tree(A):
    n = 2**(int(ceil(log(len(A), 2))))
    T = [(None, None)]*(2*n-1)

    for i, x in enumerate(A):
        T[n-1+i] = (x, i)

    for i in reversed(xrange(n-1)):
        T[i] = max(T[i*2+1], T[i*2+2])

    return T

def print_tree(T):
    print 'digraph {'
    for i, x in enumerate(T):
        print '    ' + str(i) + '[label="' + str(x) + '"]'
        if i*2+2 < len(T):
            print '    ' + str(i)+ '->'+ str(i*2+1)
            print '    ' + str(i)+ '->'+ str(i*2+2)

    print '}'

def find_generic(T, i, fallback, check, first, second):
    j = len(T)/2+i
    original = T[j]
    j = (j-1)/2

    #go up in the tree searching for a value that satisfies check
    while j > 0 and not check(T[second(j)], original):
        j = (j-1)/2

    #go down in the tree searching for the left/rightmost node that satisfies check
    while j*2+1<len(T):
        if check(T[first(j)], original):
            j = first(j)
        elif check(T[second(j)], original):
            j = second(j)
        else:
            return fallback

    return j-len(T)/2


def find_left(T, i, fallback):
    return find_generic(T, i, fallback, 
        lambda a, b: a[0]>b[0] and a[1]<b[1],  #value greater, index before
        lambda j: j*2+2,                       #rightmost first
        lambda j: j*2+1                        #leftmost second
    ) 


def find_right(T, i, fallback):
    return find_generic(T, i, fallback,
        lambda a, b: a[0]>=b[0] and a[1]>b[1], #value greater or equal, index after
        lambda j: j*2+1,                       #leftmost first
        lambda j: j*2+2                        #rightmost second
    )       

def make_frequency_array(A):
    T = make_tree(A)

    D = defaultdict(lambda: 0)
    for i, x in enumerate(A):
        left = find_left(T, i, -1)
        right = find_right(T, i, len(A))
        D[x] += (i-left) * (right-i)

    F = sorted(D.items())
    for i in range(1, len(F)):
        F[i] = (F[i][0], F[i-1][1] + F[i][1])

    return F

def query(F, n):
    idx = bisect.bisect(F, (n,))
    if idx>=len(F): return F[-1][1]
    if F[idx][0]!=n: return 0
    return F[idx][1]

F = make_frequency_array([1,2,3])
print query(F, 3)-query(F, 2) #3 3
print query(F, float('inf'))-query(F, 4) #1 4
print query(F, float('inf'))-query(F, 1) #1 1
print query(F, 2) #2 3

Ответ 3

Создайте сортированную карту значений для индекса. Например,

[34,5,67,10,100] => {5:1, 10:3, 34:0, 67:2, 100:4}

Предварительно рассчитайте запросы в два прохода над картой значений для индекса:

  • Сверху вниз - поддерживайте увеличенное дерево интервалов. Каждый раз, когда добавляется индекс,   разделить соответствующий интервал и вычесть соответствующие сегменты из общего числа:

    indexes    intervals    total sub-arrays with maximum greater than
    4          (0,3)        67 => 15 - (4*5/2)            = 5
    2,4        (0,1)(3,3)   34 => 5 + (4*5/2) - 2*3/2 - 1 = 11 
    0,2,4      (1,1)(3,3)   10 => 11 + 2*3/2 - 1          = 13
    3,0,2,4    (1,1)         5 => 13 + 1                  = 14
    
  • Внизу - поддерживайте увеличенное дерево интервалов. Каждый раз, когда добавляется индекс,   отрегулируйте соответствующий интервал и добавьте соответствующие сегменты в общее количество:

    indexes    intervals    total sub-arrays with maximum less than
    1          (1,1)         10 => 1*2/2         = 1
    1,3        (1,1)(3,3)    34 => 1 + 1*2/2     = 2 
    0,1,3      (0,1)(3,3)    67 => 2 - 1 + 2*3/2 = 4
    0,1,3,2    (0,3)        100 => 4 - 4 + 4*5/2 = 10
    
  • Третий запрос может быть предварительно рассчитан вместе со вторым:

    indexes    intervals    total sub-arrays with maximum exactly
    1          (1,1)         5 =>         1
    1,3        (3,3)        10 =>         1 
    0,1,3      (0,1)        34 =>         2
    0,1,3,2    (0,3)        67 => 3 + 3 = 6
    

Вставка и удаление в добавленные деревья имеют временную сложность O(log n). Общая временная сложность вычислений O(n log n). Каждый запрос после этого должен быть O(log n) временной сложностью.

Ответ 4

Ваша проблема может быть разделена на несколько этапов:

  • Для каждого элемента начального массива вычислить количество "подмассивов", где текущий элемент максимален. Это будет связано с комбинаторикой. Сначала вам нужно, чтобы каждый элемент знал индекс предыдущего и следующего элемента, который больше, чем текущий элемент. Затем вычислите количество подмассивов как (i - я prev) * (i next - i). Поиск я prev и я next требует двух обходов исходного массива: в прямом и обратном порядке. Для я prev вам нужно пройти по массиву слева направо. Во время обхода поддерживайте BST, который содержит самый большой из предыдущих элементов вместе со своим индексом. Для каждого элемента исходного массива найдите минимальный элемент в BST, который больше, чем текущий. Его индекс, сохраненный как значение, будет я prev. Затем удалите из BST все элементы, которые меньше этого тока. Эта операция должна быть O (logN), поскольку вы удаляете целые поддеревья. Этот шаг требуется, поскольку текущий элемент, который вы собираетесь добавить, будет "переопределять" весь элемент, который меньше его. Затем добавьте текущий элемент в BST с его индексом как значением. В каждый момент времени BST будет хранить нисходящую подпоследовательность предыдущих элементов, где каждый элемент больше всех его предшественников в массиве (для предыдущих элементов {1,2,44,5,2,6,26,6} BST будет хранить {44,26,6}). Обратный ход для поиска я next аналогичен.

  • После предыдущего шага у вас будут пары K → P, где K - значение некоторого элемента из исходного массива, а P - количество подмассивов, где этот элемент является maxumum. Теперь вам нужно сгруппировать эти пары через K. Это означает вычисление суммы значений P равных K элементов. Будьте осторожны с угловыми случаями, когда два элемента могут иметь одни и те же подмассивы.

  • Как Ritesh: поместите все сгруппированные K → P в массив, отсортируйте его по K и вычислите кумулятивную сумму P для каждого элемента в один проход. В этом случае ваши запросы будут бинарными поисками в этом отсортированном массиве. Каждый запрос будет выполняться в O (log (N)) времени.