3838. Частые значения

 

Задана последовательность n целых чисел a1, a2, ..., an в неубывающем порядке. Вам также заданы несколько запросов, состоящих из индексов i и j (1 ≤ ijn). Для каждого запроса определите число, которое чаще всего встречается среди ai, ..., aj.

 

Вход. Состоит из нескольких тестов. Каждый тест начинается со строки, содержащей два целых числа n и q (1 ≤ n, q ≤ 105). Следующая строка содержит n целых чисел a1, ..., an (-105ai ≤ 105). Считайте, что для каждого i Î {1, ..., n – 1}: aiai+1. Каждая из следующих q строк содержит один запрос, состоящий из двух целых значений i и j (1 ≤ ijn) – границы индексов запроса.

За последним тестом следует строка, содержащая один 0.

 

Выход. Для каждого запроса выведите одно целое число: количество вхождений чаще всего встречаемого числа в заданном интервале.

 

Пример входа

Пример выхода

10 3

-1 -1 1 1 1 1 3 10 10 10

2 3

1 10

5 10

0

1

4

3

 

 

РЕШЕНИЕ

дерево отрезков

 

Анализ алгоритма

Заметим, что входная последовательность ai уже отсортирована. Одинаковые элементы в ней всегда стоят рядом. Пусть Ti, j – количество вхождений чаще всего встречаемого числа в интервал [i, j]. Если ai = aj, то Ti, j = ji + 1. Разобьем интервал [i, j] на два: [i, k] и [k + 1, j].

·        Если akak+1, то Ti, j = max(Ti, k, Tk+1, j);

·        Если ak = ak+1, то Ti, j = max(Ti, k, Tk+1, j, количество вхождений ak в [i, k] + количество вхождений ak+1 в [k + 1, j]);

По последовательности а построим последовательность b такой же длины следующим образом. Пусть в последовательности а на позициях от l до r находится число x (и только на этих позициях). Тогда на позициях от l до r в последовательности b поставим числа rl + 1. Построим дерево отрезков по последовательности b, поддерживающее максимум на отрезке.

Рассмотрим запрос (Left, Right), который возвращает количество вхождений чаще всего встречаемого числа в интервале [Left, Right].

1. Если aLeft = aRight, то TLeft,Right = RightLeft + 1.

2. Иначе при помощи бинарного поиска найдем максимальный индекс LeftEnd, для которого aLeftEnd = aLeft. А также найдем минимальный индекс RightBegin, для которого aRightBegin = aRight. Остается подсчитать, сколько раз встречаются на интервале [Left, Right] числа aLeft и aRight. А также рекурсивно решить задачу для интервала [LeftEnd + 1, RightBegin – 1]. Среди трех полученных значений остается найти наибольшее. То есть

TLeft, Right = max(TLeft, LeftEnd, TRightBegin, Right, TLeftEnd + 1, RightBegin – 1)

 

Время работы алгоритма

Построение последовательности b: O(n);

Построение дерева отрезков по последовательности b: O(n);

Время выполнения одного запроса: O(log2n);

Итоговое время работы алгоритма: O(n + q log2n).

 

Сложность по памяти: O(n)

 

Пример

Для последовательности а, приведенной в примере, последовательность b примет вид:

2 2 4 4 4 4 1 3 3 3

Рассмотрим запрос на интервале [Left, Right] = [5, 10].

LeftEnd = 6, все элементы ai с индексами от 5 до 6 равны 1. Таких элементов 2.

RightBegin = 8, все элементы ai с индексами от 8 до 10 равны 10. Таких элементов 3.

Запускаем рекурсивно запрос на интервале [LeftEnd + 1, RightBegin – 1] = [7, 7]. Результатом запроса будет значение 1.

Таким образом, ответом на запрос (5, 10) будет максимум среди трех значений 2, 3 и 1.

 

Реализация алгоритма

Дерево отрезков храним в массиве SegmentTree длины 4*MAX, где MAX – максимальное количество элементов, которое может храниться в отрезке. Дерево будет поддерживать максимум на отрезке. Объявим также массивы a и b, которые будут хранить соответствующие последовательности.

 

#define MAX 100010

int SegmentTree[4*MAX];

int a[MAX], b[MAX];

 

Функция BuildTree строит дерево отрезков по массиву a. Ей передаются номер текущей вершины дерева Vertex и границы отрезка LeftPos и RightPos, соответствующие вершине Vertex.

 

void BuildTree(int *a, int Vertex, int LeftPos, int RightPos)

{

  if (LeftPos == RightPos) SegmentTree[Vertex] = a[LeftPos];

  else

  {

    int Middle = (LeftPos + RightPos) / 2;

    BuildTree(a, 2*Vertex, LeftPos, Middle);

    BuildTree(a, 2*Vertex+1, Middle+1, RightPos);

    SegmentTree[Vertex] = 

      max(SegmentTree[2*Vertex],SegmentTree[2*Vertex+1]);

  }

}

 

Функция GetMax возвращает максимум на отрезке [Left; Right]. Вершине Vertex соответствует отрезок [LeftPos; RightPos].

 

int GetMax(int Vertex, int LeftPos, int RightPos, int Left, int Right)

{

  if (Left > Right) return -INF;

  if ((Left == LeftPos) && (Right == RightPos))

    return SegmentTree[Vertex];

  int Middle = (LeftPos + RightPos) / 2;

  return max(GetMax(2*Vertex, LeftPos, Middle, Left,

                    min(Right,Middle)),

             GetMax(2*Vertex+1, Middle+1, RightPos,

                    max(Left,Middle+1),Right));

}

 

Основная часть программы. Читаем входную последовательность ai в массив а.

 

while(scanf("%d %d",&n,&q), n)

{

  for(i = 0; i < n; i++) scanf("%d",&a[i]);

 

По массиву a строим массив b. В переменной cnt подсчитываем количество подряд идущих одинаковых элементов.

 

  for(bPtr = i = 0; i < n; i++)

  {

    cnt = 1;

    while((i < n) && (a[i] == a[i+1])) cnt++, i++;

    for(j = 0; j < cnt; j++) b[bPtr++] = cnt;

  }

 

Из элементов последовательности b строим дерево отрезков, поддерживающее максимум.

 

  memset(SegmentTree,0,sizeof(SegmentTree));

  BuildTree(b,1,0,n-1);

 

Читаем запрос (Left, Right). Сдвигаем границы запроса на единицу влево, так как индексация массивов а и b начинается с нуля.

 

  for(i = 0; i < q; i++)

  {

    scanf("%d %d",&Left,&Right);

    Left--; Right--;

 

Если a[Left] = a[Right], то все элементы на отрезке [Left, Right] одинаковы. Возвращаем длину этого отрезка.

 

    if (a[Left] == a[Right]) printf("%d\n",Right - Left + 1);

    else

    {

 

При помощи бинарного поиска находим максимальный индекс LeftEnd, для которого aLeftEnd = aLeft.

 

     int LeftEnd = (int)(upper_bound(a+Left,a+Right,a[Left]) - a) - 1;

 

При помощи бинарного поиска находим минимальный индекс RightBegin, для которого aRightBegin = aRight.

 

     int RightBegin = (int)(lower_bound(a+Left,a+Right,a[Right]) - a);

 

На отрезке [Left, Right] число aLeft встречается LeftCnt раз.

 

     int LeftCnt = LeftEnd - Left + 1;

 

На отрезке [Left, Right] число aRight встречается RightCnt раз.

 

     int RightCnt = Right - RightBegin + 1;

 

Решаем рекурсивно задачу для интервала [LeftEnd + 1, RightBegin – 1].

 

     int Middle = GetMax(1,0,n-1,LeftEnd + 1, RightBegin - 1);

 

Среди трех полученных значений LeftCnt, RightCnt и Middle находим наибольшее и выводим ответ.

 

     int res = max(max(LeftCnt,RightCnt),Middle);

     printf("%d\n",res);

    }

  }

}