<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=188.130.155.161&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=188.130.155.161&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/188.130.155.161"/>
		<updated>2026-04-09T04:00:41Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9C%D0%BE&amp;diff=81135</id>
		<title>Алгоритм Мо</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9C%D0%BE&amp;diff=81135"/>
				<updated>2021-07-11T09:57:22Z</updated>
		
		<summary type="html">&lt;p&gt;188.130.155.161: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''Алгоритм Мо''' (англ. ''Mo's algorithm'') — применяется для решения задач, в которых требуется отвечать на запросы &amp;lt;tex&amp;gt;arr[l \ldots r]&amp;lt;/tex&amp;gt; на массиве &lt;br /&gt;
''без'' изменения элементов в оффлайн за время &amp;lt;tex&amp;gt;O(Q \cdot \log{Q} + (N + Q) \cdot \sqrt{N})&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} количество запросов,&lt;br /&gt;
а &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; {{---}} количество элементов в массиве. Характерными примерами задач на этот алгоритм являются: нахождение медианы на отрезке (число, которое встречается больше всех остальных), &lt;br /&gt;
вычисление количества инверсий на отрезке. &lt;br /&gt;
&lt;br /&gt;
==Алгоритм==&lt;br /&gt;
В каждый момент времени будем хранить непрерывный отрезок &amp;lt;tex&amp;gt;[a \ldots b]&amp;lt;/tex&amp;gt; исходного массива (будем называть его рабочим отрезком), вместе со структурой данных, &lt;br /&gt;
которая умеет обрабатывать следующие операции:&lt;br /&gt;
* &amp;lt;tex&amp;gt;\mathtt{addLeft(a-1)}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{addRight(b+1)}&amp;lt;/tex&amp;gt; {{---}} операции, которые позволяют добавить элемент в рабочий отрезок слева и справа соответственно;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\mathtt{delLeft(a)}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{delRight(b)}&amp;lt;/tex&amp;gt; {{---}} операции, которые позволяют удалить элемент рабочего отрезка слева и справа соответственно;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\mathtt{answer}&amp;lt;/tex&amp;gt; {{---}} операция, которая позволяет получить ответ на запрос, если бы его границами был рабочий отрезок.&lt;br /&gt;
&lt;br /&gt;
Изначально в качестве рабочего отрезка можно взять любой отрезок. Для удобства чтения будем считать изначальным отрезок &amp;lt;tex&amp;gt;[1;1)&amp;lt;/tex&amp;gt;, то есть &amp;lt;tex&amp;gt;a = 1&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;b = 0&amp;lt;/tex&amp;gt;, фактически {{---}} пустой отрезок.&lt;br /&gt;
&lt;br /&gt;
Запишем все запросы в массив, отсортируем их определённым способом (который будет описан ниже) будем их обрабатывать в том порядке, в котором они будут лежать в массиве после [[Сортировки | сортировки]].&lt;br /&gt;
&lt;br /&gt;
Допустим, что текущий рабочий отрезок — &amp;lt;tex&amp;gt;[a \ldots b]&amp;lt;/tex&amp;gt;, а первый необработанный запрос — &amp;lt;tex&amp;gt;[l_i, r_i]&amp;lt;/tex&amp;gt; тогда рассмотрим случаи: &lt;br /&gt;
* Если изначально было &amp;lt;tex&amp;gt; a &amp;gt; l_i &amp;lt;/tex&amp;gt;, то будем добавлять в рабочий отрезок элементы слева по одному, пока граница не совпадёт;&lt;br /&gt;
* Если же это не так, то есть &amp;lt;tex&amp;gt; a &amp;lt; l_i &amp;lt;/tex&amp;gt; это значит, что в рабочем отрезке присутствуют те элементы, которых там быть не должно, и они должны быть удалены;&lt;br /&gt;
* При равенстве &amp;lt;tex&amp;gt;a=l_i&amp;lt;/tex&amp;gt; никаких действий с левой границей рабочего отрезка производить не потребуется.&lt;br /&gt;
&lt;br /&gt;
Аналогично поступим с &amp;lt;tex&amp;gt;b&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;r_i&amp;lt;/tex&amp;gt;. Для компактности и наглядности кода мы сначала расширим рабочий отрезок до отрезка &amp;lt;tex&amp;gt;[l \ldots r]&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;l = \min(a, l_i)&amp;lt;/tex&amp;gt;, а &amp;lt;tex&amp;gt;r = \max(b, r_i)&amp;lt;/tex&amp;gt;, а затем удалим лишние элементы при помощи операций &amp;lt;tex&amp;gt;\mathtt{delLeft}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{delRight}&amp;lt;/tex&amp;gt;, чтобы получить отрезок &amp;lt;tex&amp;gt;[l_i \ldots r_i]&amp;lt;/tex&amp;gt;, после чего вызовем &amp;lt;tex&amp;gt;\mathtt{answer}&amp;lt;/tex&amp;gt; и запомним ответ для этого запроса.&lt;br /&gt;
&lt;br /&gt;
Теперь разберём поподробнее, как именно следует сортировать запросы для достижения вышеназванной асимптотики по времени.&lt;br /&gt;
&lt;br /&gt;
Разделим все запросы на блоки размера &amp;lt;tex&amp;gt;K&amp;lt;/tex&amp;gt; по левой границе: те запросы, для которых &amp;lt;tex&amp;gt;1 \leqslant l_i \leqslant K&amp;lt;/tex&amp;gt; {{---}} попадают в первую группу, &lt;br /&gt;
те запросы, для которых &amp;lt;tex&amp;gt;K + 1 \leqslant l_i \leqslant 2 \cdot K&amp;lt;/tex&amp;gt; {{---}} во вторую, &amp;lt;tex&amp;gt;2 \cdot K + 1 \leqslant l_i \leqslant 3 \cdot K&amp;lt;/tex&amp;gt; {{---}} в третью, и так далее. Будем рассматривать все группы запросов независимо друг от друга. Если внутри каждой группы отсортировать запросы увеличению правой границы, то асимптотика по времени для обработки одной группы будет &amp;lt;tex&amp;gt;O(N + Q_i \cdot K)&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;Q_i&amp;lt;/tex&amp;gt; {{---}} количество запросов, принадлежащих группе под номером &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Доказательство==&lt;br /&gt;
Докажем, что на обработку одной группы суммарно уйдёт не больше чем &amp;lt;tex&amp;gt;3 \cdot N + Q_i \cdot K&amp;lt;/tex&amp;gt; операций &amp;lt;tex&amp;gt;\mathtt{add}&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;\mathtt{del}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для этого рассмотрим отдельно количество сделанных операций каждого из четырёх типов:&lt;br /&gt;
* изначально, до обработки группы, рабочий отрезок был &amp;lt;tex&amp;gt;[a \ldots b]&amp;lt;/tex&amp;gt;, для обработки первого запроса может потребоваться &amp;lt;tex&amp;gt;2 \cdot N&amp;lt;/tex&amp;gt; операций &amp;lt;tex&amp;gt;\mathtt{add}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{del}&amp;lt;/tex&amp;gt;;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\mathtt{delRight}&amp;lt;/tex&amp;gt; между отрезками одной группы не произойдёт ни разу, так как рабочий отрезок внутри одной группы будет только расширяться в сторону правого конца;&lt;br /&gt;
* &amp;lt;tex&amp;gt;\mathtt{addRight}&amp;lt;/tex&amp;gt; в этой группе произойдёт суммарно не больше чем &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; раз, так как минимальная правая граница {{---}} &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;, а максимальная {{---}} &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;;&lt;br /&gt;
* для оставшихся двух операций рассмотрим два последовательных запроса &amp;lt;tex&amp;gt;[l_i \ldots r_i]&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;[l_j \ldots r_j]&amp;lt;/tex&amp;gt;. Нетрудно заметить, что так как отрезки принадлежат одной группе, то &amp;lt;tex&amp;gt;|l_i - l_j| &amp;lt; K&amp;lt;/tex&amp;gt;, следовательно, количество операций &amp;lt;tex&amp;gt;\mathtt{addLeft}&amp;lt;/tex&amp;gt; или &amp;lt;tex&amp;gt;\mathtt{delLeft}&amp;lt;/tex&amp;gt; не будет превосходить &amp;lt;tex&amp;gt;K&amp;lt;/tex&amp;gt;, а суммарно для всей группы {{---}} &amp;lt;tex&amp;gt;Q_i \cdot K&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Таким образом, нетрудно видеть, все группы будут обработаны за время &amp;lt;tex&amp;gt;O \left( \dfrac{N^2}{K}  + K \cdot Q \right) &amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
При выборе &amp;lt;tex&amp;gt;K = \sqrt{N}&amp;lt;/tex&amp;gt; с учётом сортировки по правой границе получается асимптотика времени &amp;lt;tex&amp;gt;O(Q \cdot \log Q + (N + Q) \cdot \sqrt N)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Реализация==&lt;br /&gt;
 '''struct''' Query:&lt;br /&gt;
   '''int''' l, r, index &lt;br /&gt;
 &lt;br /&gt;
 '''int''' K = sqrt(N)&lt;br /&gt;
 '''int''' a = 1, b = 0 &amp;lt;font color=green&amp;gt;// создаём пустой рабочий отрезок&amp;lt;/font&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 '''bool''' isLess('''Query''' a, '''Query''' b):&lt;br /&gt;
   '''if''' a.l / K != b.l / K:&lt;br /&gt;
     '''return''' a.l &amp;lt; b.l&lt;br /&gt;
   '''return''' a.r &amp;lt; b.r &lt;br /&gt;
 &lt;br /&gt;
 '''function''' process('''Query'''[Q] q):&lt;br /&gt;
   sort(q, isLess) &amp;lt;font color=green&amp;gt;// сортируем запросы, используя функцию '''isLess''' как оператор сравнения&amp;lt;/font&amp;gt;&lt;br /&gt;
   '''for''' i = 0 '''to''' Q - 1:&lt;br /&gt;
     '''while''' a &amp;gt; q[i].l:&lt;br /&gt;
       addLeft(a - 1)&lt;br /&gt;
       a -= 1&lt;br /&gt;
     '''while''' b &amp;lt; q[i].r:&lt;br /&gt;
       addRight(b + 1)&lt;br /&gt;
       b += 1&lt;br /&gt;
     '''while''' a &amp;lt; q[i].l:&lt;br /&gt;
       delLeft(a)&lt;br /&gt;
       a += 1&lt;br /&gt;
     '''while''' b &amp;gt; q[i].r:&lt;br /&gt;
       delRight(b)&lt;br /&gt;
       b -= 1&lt;br /&gt;
     result[q[i].id] = answer() &amp;lt;font color=green&amp;gt;// получаем ответ на [a...b] &amp;lt;/font&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Рассмотрим для наглядности решение задачи нахождения моды на отрезке:&lt;br /&gt;
&lt;br /&gt;
Будем использовать код описанный выше, осталось только описать операции &amp;lt;tex&amp;gt;\mathtt{addLeft}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{addRight}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{delLeft}&amp;lt;/tex&amp;gt;, &amp;lt;tex&amp;gt;\mathtt{delRight}&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Так как в данной задаче порядок чисел на отрезке не важен, важно лишь количество вхождений каждого, то реализация отдельных функций для добавления слева и справа нам не потребуется.&lt;br /&gt;
&lt;br /&gt;
Для простоты будем считать, что все числа '''не превышают &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt;''', тогда будем хранить массив &amp;lt;tex&amp;gt;cnt[N + 1]&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;cnt[value]&amp;lt;/tex&amp;gt; - количество вхождений числа &amp;lt;tex&amp;gt;value&amp;lt;/tex&amp;gt; в рабочем отрезке. Будем помимо этого массива хранить отсортированное множество &amp;lt;tex&amp;gt;current&amp;lt;/tex&amp;gt;, в котором будут содержаться все пары вида &amp;lt;tex&amp;gt;\langle \mathtt{cnt[value]}, \mathtt{value} \rangle&amp;lt;/tex&amp;gt;, для ненулевых &amp;lt;tex&amp;gt;cnt[value]&amp;lt;/tex&amp;gt;. Реализовать его можно, например, используя [[Красно-черное_дерево | красно-черное дерево]] Тогда операции будут иметь следующий вид:&lt;br /&gt;
 '''function''' add('''int''' index):&lt;br /&gt;
   '''int''' value = arr[index]&lt;br /&gt;
   '''if''' cnt[value] &amp;gt; 0:&lt;br /&gt;
     current.erase((cnt[value], value))&lt;br /&gt;
   cnt[a[index]] += 1&lt;br /&gt;
   current.insert((cnt[value], value))&lt;br /&gt;
 &lt;br /&gt;
 '''function''' del('''int''' index):&lt;br /&gt;
   '''int''' value = arr[index]&lt;br /&gt;
   current.erase((cnt[value], value))&lt;br /&gt;
   cnt[a[index]] -= 1&lt;br /&gt;
   '''if''' cnt[value] &amp;gt; 0:&lt;br /&gt;
     current.insert((cnt[value], value))&lt;br /&gt;
 &lt;br /&gt;
 '''function''' answer(): '''int'''&lt;br /&gt;
   '''return''' current.max.second &amp;lt;font color=green&amp;gt;// находим максимальную пару в множестве&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Итоговая асимптотика решения: &amp;lt;tex&amp;gt;O(Q \cdot \log Q + (N + Q) \cdot \sqrt{N} \cdot \log N)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
* [[Статистики_на_отрезках._Корневая_эвристика | Корневая эвристика]]&lt;br /&gt;
* [[Решение_RMQ_с_помощью_разреженной_таблицы#.D0.A0.D0.B0.D0.B7.D1.80.D0.B5.D0.B6.D0.B5.D0.BD.D0.BD.D0.B0.D1.8F_.D1.82.D0.B0.D0.B1.D0.BB.D0.B8.D1.86.D0.B0 | Разреженная таблица]]&lt;br /&gt;
&lt;br /&gt;
==Источники информации==&lt;br /&gt;
* [https://www.hackerearth.com/practice/notes/mos-algorithm/ HackerEarth - Mo's algorithm]                              &lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория:Сортировки]]&lt;br /&gt;
[[Категория: Задача о наименьшем общем предке]]&lt;/div&gt;</summary>
		<author><name>188.130.155.161</name></author>	</entry>

	</feed>