процесс Давайте как наши запросы и наши элементы в левой направо таким образом, что-то вроде
for (int i = 0; i < N; ++i) {
// 1. Add a[i] to all internal data structures
// 2. Calculate answers for all queries q such that r[q] == i
}
Здесь мы имеем O(N)
итерации этого цикла, и мы хотим сделать и обновление структуры данных и запросите ответ для суффикса текущей обрабатываемой части в o(N)
времени.
Давайте использовать массив contains[i][j]
, который имеет 1
если суффикс, начинающийся в позиции i
содержит номер j
и 0
иначе. Рассмотрим также, что мы рассчитали префиксные суммы для каждого contains[i]
отдельно. В этом случае мы могли бы ответить на каждый конкретный суффикс-запрос в O(log N)
времени, используя двоичный поиск: мы должны просто найти первый нуль в соответствующем массиве contains[l[i]]
, который является точно первой позицией, где частичная сумма равна индексу, а не индексировать + 1 К сожалению, такие массивы занимали бы пространство O(N^2)
и нуждались бы в O(N^2)
времени для каждого обновления.
Итак, мы должны оптимизировать. Давайте построим 2-мерный range tree с операциями диапазона «запрос суммы» и «присвоения». В таком дереве мы можем запросить сумму на любом суб-прямоугольнике и присваивать одно и то же значение всем элементам любого суб-прямоугольника в O(log^2 N)
времени, что позволяет нам обновлять в O(log^2 N)
время и запросы в O(log^3 N)
времени, что дает временную сложность O(Nlog^2 N + Qlog^3 N)
. Пространственная сложность O((N + Q)log^2 N)
(и то же самое время для инициализации массивов) достигается с помощью ленивой инициализации.
UP: Давайте рассмотрим, как работает запрос в деревьях диапазона с «суммой».Для 1-мерного дерева (не сделать этот ответ слишком долго), это что-то вроде этого:
class Tree
{
int l, r; // begin and end of the interval represented by this vertex
int sum; // already calculated sum
int overriden; // value of override or special constant
Tree *left, *right; // pointers to children
}
// returns sum of the part of this subtree that lies between from and to
int Tree::get(int from, int to)
{
if (from > r || to < l) // no intersection
{
return 0;
}
if (l <= from && to <= r) // whole subtree lies within the interval
{
return sum;
}
if (overriden != NO_OVERRIDE) // should push override to children
{
left->overriden = right->overriden = overriden;
left->sum = right->sum = (r - l)/2 * overriden;
overriden = NO_OVERRIDE;
}
return left->get(from, to) + right->get(from, to); // split to 2 queries
}
Учитывая, что в нашем конкретном случае все запросы к дереву являются префиксами запросов суммы, from
всегда равен 0
, поэтому один из вызовов детям всегда возвращает тривиальный ответ (0
или уже вычисленный sum
). Итак, вместо того, чтобы делать O(log N)
запросы к двумерному дереву в алгоритме бинарного поиска, мы могли бы реализовать специальную процедуру для поиска, очень похожую на этот запрос get
. Сначала он должен получить значение левого ребенка (который принимает O(1)
, поскольку он уже рассчитан), а затем проверьте, находится ли узел, который мы ищем, слева (эта сумма меньше числа листов в левом поддереве) и идет влево или вправо на основании этой информации. Этот подход будет дополнительно оптимизировать запрос до O(log^2 N)
времени (так как теперь это одна операция с деревом), что дает сложность O((N + Q)log^2 N))
как время, так и пространство.
Не уверен, что это решение достаточно быстро для обоих Q
и N
до 10^5
, но, возможно, оно будет еще более оптимизировано.
Я прав, что вы знаете все запросы перед обработкой первого запроса? Я имею в виду, пакетный процесс алгоритм essing в порядке, и вам не нужны он-лайн ответы – alexeykuzmin0
автономный алгоритм в порядке. – square1001
Это какой-то сегментный вопрос? – Arunmu