diff --git a/3_intersection_of_rectangles/img/1_slide.svg b/3_intersection_of_rectangles/img/1_slide.svg
new file mode 100644
index 0000000..35945c6
--- /dev/null
+++ b/3_intersection_of_rectangles/img/1_slide.svg
@@ -0,0 +1,135 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/img/2_slide.svg b/3_intersection_of_rectangles/img/2_slide.svg
new file mode 100644
index 0000000..236b8ad
--- /dev/null
+++ b/3_intersection_of_rectangles/img/2_slide.svg
@@ -0,0 +1,906 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/img/modification.svg b/3_intersection_of_rectangles/img/modification.svg
new file mode 100644
index 0000000..e53be84
--- /dev/null
+++ b/3_intersection_of_rectangles/img/modification.svg
@@ -0,0 +1,374 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/img/priority_search_tree.svg b/3_intersection_of_rectangles/img/priority_search_tree.svg
new file mode 100644
index 0000000..20986c3
--- /dev/null
+++ b/3_intersection_of_rectangles/img/priority_search_tree.svg
@@ -0,0 +1,489 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/img/priority_search_tree_a.svg b/3_intersection_of_rectangles/img/priority_search_tree_a.svg
new file mode 100644
index 0000000..6137248
--- /dev/null
+++ b/3_intersection_of_rectangles/img/priority_search_tree_a.svg
@@ -0,0 +1,208 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/img/priority_search_tree_b.svg b/3_intersection_of_rectangles/img/priority_search_tree_b.svg
new file mode 100644
index 0000000..d6b2687
--- /dev/null
+++ b/3_intersection_of_rectangles/img/priority_search_tree_b.svg
@@ -0,0 +1,353 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/img/rirst.svg b/3_intersection_of_rectangles/img/rirst.svg
new file mode 100644
index 0000000..e37b363
--- /dev/null
+++ b/3_intersection_of_rectangles/img/rirst.svg
@@ -0,0 +1,134 @@
+
+
+
+
diff --git a/3_intersection_of_rectangles/intersection_of_rectangles.ipynb b/3_intersection_of_rectangles/intersection_of_rectangles.ipynb
new file mode 100644
index 0000000..70beb81
--- /dev/null
+++ b/3_intersection_of_rectangles/intersection_of_rectangles.ipynb
@@ -0,0 +1,479 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Пересечение прямоугольника с множеством прямоугольников\n",
+ "\n",
+ "## Постановка задачи\n",
+ "\n",
+ "В данном билете будет описываться решение третьего случая в рамках задачи \"Пересечение прямоугольника с множеством прямоугольников\". Все случаи описаны во [2-м билете](../2_rect_intersect/rect_intersect.ipynb).\n",
+ "\n",
+ "\n",
+ "Дано множество прямоугольников с ребрами, параллельными осям координат. Дан прямоугольник запроса с таким же свойством. Необходимо найти все прямоугольники из множества, которые пересекаются с прямоугольником запроса ребрами. Утверждается, что нет прямоугольников, чьи концы лежат внутри прямоугольника запроса.\n",
+ "\n",
+ "Сведем задачу к меньшей: \n",
+ "Пусть есть множество таких отрезков, что каждый параллелен какой-либо оси, и прямоугольник запроса. Необходимо ответить, какие отрезки пересекают данный прямоугольник. Известно, что нет отрезка, чей конец лежит внутри прямоугольника, следовательно, отрезок должен пересекать либо две горизонтальные, либо две вертикальные грани. Поэтому достаточно проверить пересечение отрезков с одной горизотальной и с одной вертикальной гранями. Рассмотрим, например, пересечение с вертикальной (с горизонтальной задача решается аналогично). \n",
+ "\n",
+ "Итого мы имеем:\n",
+ "* Множество горизонтальных отрезков\n",
+ "* Один вертикальный отрезок\n",
+ "\n",
+ "И нам необходимо найти подмножество отрезков, пересекающих данный вертикальный.\n",
+ "\n",
+ "Решить данную задачу можно, используя следующие структуры данных:\n",
+ "* Дерево интервалов (interval tree)\n",
+ "* Приоритетное дерево поиска (priority search tree)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Дерево интервалов (interval tree)\n",
+ "\n",
+ "Рассмотрим множество $S$ интервалов, $S = \\{I_{i}|i = 1,2,....n\\}$, каждый из которых определен упорядоченной парой, $I_{i} = [l_{i},r_{i}],l_{i},r_{i} \\in R, l_{i} \\lt r_{i}, i = 1,2,...,n$\n",
+ "\n",
+ "Интервальное дерево, $Interval\\_Tree(S)$, для $S$ — это дополненное бинарное дерево, в котором каждая вершина $v$ имеет свой ключ, $v.key$, два указателя, $v.left$ и $v.right$, на левое и правое поддеревья, и вспомогательный указатель, $v.aux$, на дополнительную структуру. Рекурсивно дерево задается следующим образом:\n",
+ "\n",
+ "* Корень дерева $v$, ассоциируемый с множеством $S$ и обозначаемый как $Interval\\_Tree\\_root(S)$, имеет ключевое значение $v.key$ равное медиане $2 \\times |S|$ вершин. Данное ключевое значение $v.key$ разделяет $S$ на три подмножества $S_{l}, S_{r}$ и $S_{m}$, состоящие из множеств интервалов, лежащих полностью слева от $v.key$, лежащих полностью справа от $v.key$ и включающих в себя $v.key$, соответственно. Таким образом, $S_{l} = \\{I_{i}|r_{i} \\lt v.key\\}$, $S_{r} = \\{I_{j}|v.key \\lt l_{j}\\}$ и $S_{k} = \\{I_{k}|r_{k} \\leqslant v.key \\leqslant r_{k}\\}$\n",
+ "\n",
+ "* Указатель $v.left$ указывает на левое поддерево $(Interval\\_Tree\\_root(S_{l}))$ и $v.right$ указывает на правое поддерево $(Interval\\_Tree\\_root(S_{r}))$\n",
+ "\n",
+ "* Вспомогательный указатель $v.aux$ указывает на вспомогательную структуру, состоящую из двух отсортированных массивов (SA - Sorted Array), $SA(S_{m}.left)$ и $SA(S_{m}.right)$, состоящие из левых концов интервалов $S_{m}$ и правых концов интервалов $S_{m}$. Таким образом, $S_{m}.left = \\{l_{i}|I_{i} \\in S_{m}\\}$ и $S_{m}.right = \\{r_{i}|I_{i} \\in S_{m}\\}$. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Построение дерева интервалов\n",
+ "\n",
+ "Ниже приведенный код рекурсивно строит дерево интервалов множества $S$, состоящее из $n$ интервалов. Смотрите рис. 1(а) с примером.\n",
+ "\n",
+ "**function** Interval\\_Tree(S) \n",
+ "/\\* Она возвращает указатель $v$ на корень, $Interval\\_Tree\\_root(S)$, дерева интервалов для множества интервалов $S$. \\*/\n",
+ "\n",
+ "**Input:** Множество $S$, содержащее $n$ интервалов, $S = \\{I_{i}|i = 1,2,....n\\}$, и каждый интервал $I_{i} = [l_{i},r_{i}]$, где $l_{i}$ и $r_{i}$ — левый и правый конец $I_{i}$, $l_{i}, r_{i} \\in R$ и $l_{i} \\lt r_{i}, i = 1,2,...,n$.\n",
+ "\n",
+ "**Output:** Дерево интервалов с корнем, равным $Interval\\_Tree\\_root(S)$\n",
+ "\n",
+ "**Pseudocode:**\n",
+ "1. Если $S = \\varnothing$, вернуть $nil$.\n",
+ "2. Создать вершину $v$ такую, что $v.key$ равен $x$, где $x$ — это медиана множества концов отрезков. Определим $S_{l} = \\{I_{i}|r_{i} < x\\}$, $S_{r} = \\{I_{j}|x \\lt l_{j}\\}$ и $S_{m} = \\{I_{k}|r_{k} \\leqslant x \\leqslant r_{k}\\}$, $|S_{l}| \\leqslant \\frac{|S|}{2}$ и $|S_{r}| \\leqslant \\frac{|S|}{2}$.\n",
+ "3. Присвоим $v.left = Interval\\_Tree(S_{l})$\n",
+ "4. Присвоим $v.right = Interval\\_Tree(S_{r})$ \n",
+ "5. Создать вершину $w$, которая является корневой вершиной вспомогательной структуры, ассоциированной с множеством $S_{m}$, такой, что $w.left$ и $w.right$ указывают на два отсортированных массива, $SA(S_{m}.left)$ и $SA(S_{m}.right)$. $SA(S_{m}.left)$ обозначает массив левых концов интервалов из $S_{m}$ в возрастающем порядке, и $SA(S_{m}.right)$ — массив правых концов интервалов из $S_{m}$ в порядке убывания.\n",
+ "6. Множество $v.aux$ приравнять к вершине $w$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Node:\n",
+ "\n",
+ " def __init__(self, key):\n",
+ " self.key = key\n",
+ "\n",
+ "class Auxiliary:\n",
+ "\n",
+ " def __init__(self, S):\n",
+ " self.left = sorted(S, key=lambda x: x[0])\n",
+ " self.right = sorted(S, key=lambda x: x[1], reverse=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def interval_tree(S):\n",
+ " if not S:\n",
+ " return None\n",
+ " v = Node(median([x for interval in S for x in interval]))\n",
+ " v.left = interval_tree(list(filter(lambda x: x[1] < v.key, S)))\n",
+ " v.right = interval_tree(list(filter(lambda x: v.key < x[0], S)))\n",
+ " v.aux = Auxiliary(list(filter(lambda x: x[0] <= v.key <= x[1], S)))\n",
+ " return v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Заметьте, что это рекурсивное построение дерева интервалов имеет высоту $O(\\log n)$ и требует $O(n)$ памяти, где $n$ — мощность множества $S$, так как каждый интервал либо в левом поддереве, либо в правом поддереве, либо в дополнительной структуре.\n",
+ "\n",
+ "Строится дерево за $O(n \\log n)$. Построение состоит из $O(\\log n)$ рекурсивных вызовов, в каждом из которых происходит поиск медианы за $O(n)$. Также тратится время на сортировку массивов, но их суммарная длина равна $O(n)$, таким образом суммарное время на сортировку равно $O(n\\log n)$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Пример и применение\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "
Рис. 1
\n",
+ "
\n",
+ "\n",
+ "Рис. 1(b) показывает пример дерева интервалов для множества интервалов, проиллюстрированного на рис. 1(a).\n",
+ "Дерево интервалов может быть использовано для быстрого ответа на запросы по следующим задачам.\n",
+ "\n",
+ "**Проблема поиска прилежащего интервала (Enclosing Interval Searching Problem)** Даны множество $S$, содержащее $n$ интервалов, и точка $q$. Необходимо найти все интервалы, содержащие $q$, то есть найти подмножество $F \\subseteq S$ такое, что $F = \\{I_{i}|l_{i} \\leqslant q \\leqslant r_{i}\\}$.\n",
+ "\n",
+ "**Проблема поиска перекрывающего интервала (Overlapping Interval Searching Problem)** Даны множество $S$, содержащее $n$ интервалов, и интервал $Q$. Найти все интервалы из $S$, пересекающиеся с $Q$, то есть найти подмножество $F \\subseteq S$ такое, что $F = \\{I_{i}|I_{i} \\cap Q \\neq \\varnothing \\}$.\n",
+ "\n",
+ "Ниже приведенный псевдокод решает **Проблему поиска перекрывающего интервала** за $O(\\log n) + |F|$. Он запускается вызовом метода $Overlapping\\_Interval\\_Search(v, Q, F)$, где $v$ — $Interval\\_Tree\\_root(S)$, а F, изначально приравненное к $\\varnothing$, будет преставлять собой множество интервалов, пересекающихся с $Q$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**function** $Overlapping\\_Interval\\_Search(v, Q, F)$\n",
+ "\n",
+ "**Input:** Множество $S$, состоящее из $n$ интервалов, $S = \\{I_{i}|i = 1,2,...,n\\}$ и каждый интервал $I_{i} = [l_{i},r_{i}]$, где $l_{i}$ и $r_{i}$ — левый и правый конец $I_{i}$, $l_{i}, r_{i} \\in R$ и $l_{i} \\lt r_{i}, i = 1,2,...,n$, а такоже интервал $Q = [l, r], l,r \\in R$.\n",
+ "\n",
+ "**Output:** Подмножество $F = \\{I_{i}|I_{i} \\cap Q \\neq \\varnothing \\}$.\n",
+ "\n",
+ "**Pseudocode:**\n",
+ "1. Приравнять $F = \\varnothing$ изначально\n",
+ "2. Если $v$ равен $nil$, то закончить\n",
+ "3. $if$ $(v.key \\in Q)$ then\n",
+ "
\n",
+ "для каждого интервала $I_{i}$ во вспомогательной структуре, на которую указывает $v.aux$, выполнить $F = F \\cup \\{I_{i}\\}$\n",
+ "$Overlapping\\_Interval\\_Search(v.left, Q, F)$ \n",
+ "$Overlapping\\_Interval\\_Search(v.right, Q, F)$\n",
+ "
\n",
+ "4. $if$ $(r \\lt v.key)$ then\n",
+ "
\n",
+ "для каждого левого конца $l_{i}$ в отсортированном массиве, на который указывает $v.aux.left$, такого, что $l_{i} \\leqslant r$ выполнить $F = F \\cup \\{I_{i}\\}$\n",
+ "$Overlapping\\_Interval\\_Search(v.left, Q, F)$
\n",
+ "для каждого правого конца $l_{i}$ в отсортированном массиве, на который указывает $v.aux.right$, такого, что $r_{i} \\geqslant l$ выполнить $F = F \\cup \\{I_{i}\\}$\n",
+ "$Overlapping\\_Interval\\_Search(v.right, Q, F)$
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def overlapping_interval_search(v, Q, F):\n",
+ " if v is None:\n",
+ " return\n",
+ " if Q[0] <= v.key <= Q[1]:\n",
+ " F.extend(v.aux.left)\n",
+ " overlapping_interval_search(v.left, Q, F)\n",
+ " overlapping_interval_search(v.right, Q, F)\n",
+ " if Q[1] < v.key:\n",
+ " for i in range(len(v.aux.left)):\n",
+ " if v.aux.left[i][0] <= Q[1]:\n",
+ " F.append(v.aux.left[i])\n",
+ " else:\n",
+ " break\n",
+ " overlapping_interval_search(v.left, Q, F)\n",
+ " if Q[0] > v.key:\n",
+ " for i in range(len(v.aux.right)):\n",
+ " if v.aux.right[i][1] >= Q[0]:\n",
+ " F.append(v.aux.right[i])\n",
+ " else:\n",
+ " break\n",
+ " overlapping_interval_search(v.right, Q, F)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Можно легко заметить, что интервал $I$ из $S$ пересекает интевал $Q = [l,r]$, если (i) $Q$ содержит левый конец $I$, (ii) $Q$ содержит правый конец $I$, или (iii) $Q$ полностью содержится в $I$. Шаг 3 получает все интервалы $I$, которые содержат точку $v.key$, которая также содержится в Q. Шаг 4 получает все интервалы, отвечающие условиям (i) или (iii), и шаг 5 получает все интервалы, отвечающие условиям (ii) или (iii).\n",
+ "\n",
+ "Заметьте, что в особом случае работы процедуры $Overlapping\\_Interval\\_Search(v, Q, F)$, когда мы подставляем интервал $Q = [l,r]$ такой, что его левый и правый концы совпадают, то есть $l=r$, мы получим все интервалы из $S$, содержащие точку. Таким образом решается **Проблема поиска прилежащего интервала**.\n",
+ "\n",
+ "Однако, если кто-то заинтересован в решении проблемы поиска специального типа пересечения интервалов, например, всех интервалов содержащихся в или содержащих данный интервал, дерево интервалов не дает необходимого эффективного решения. Суммарно, дерево интервалов не предоставляет эффективного метода решения задачи о множестве интервалов, например максимальное пересечение (maximum clique) или измерение максимальной длины объединения множества интервалов.\n",
+ "\n",
+ "Мы закончим данный пункт следующей теоремой.\n",
+ "\n",
+ "#### Теорема\n",
+ "> **Проблема поиска прилежащего интервала (Enclosing Interval Searching Problem)** и **Проблема поиска перекрывающего интервала (Overlapping Interval Searching Problem)** для множества $S$, состоящего из $n$ интервалов, могут быть решены за $O(\\log n)$ (плюс время для вывода), используя $O(n)$ памяти.\n",
+ "\n",
+ "$\\triangleright$\n",
+ "
\n",
+ "В каждом усле мы тратим $O(k_{v})$ ($k_{v}$ - количество подходящих нам отрезков, хранящихся в вершине $v$) на вывод всех подходящих отрезков в нем и делаем рекурсивный вызов. Так как рекурсивных вызовов будет $O(\\log n)$, суммарное время работы: $O(\\log n) + \\sum O(k_{v}) = O(\\log n + k)$. \n",
+ "Каждый конец отрезка хранится только один раз и количество вершин в дереве не превосходит количества отрезков, следовательно, вся структура занимает $O(n)$ памяти.\n",
+ "
\n",
+ "$\\triangleleft$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Модификация для решения данной задачи\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "
Вид запроса
\n",
+ "
\n",
+ "\n",
+ "Мы научились находить все горизонтальные отрезки, пересекающие вертикальную прямую запроса. Но нам нужно научиться учитывать еще и $y$ координату, так как в исходной задаче нам нужно уметь находить пересечения горизонтальных отрезков с вертикальным отрезком, а не всей прямой. То есть нам нужно в процессе поиска уметь отбирать отрезки, которые дополнительно еще попадают в определенный промежуток по $y$ координате.\n",
+ "\n",
+ "Для этого вместо двух отсортированных массивов будем хранить два персистентных сбалансированных дерева поиска по $y$ координате (в которых версия дерева соответствует $x$ координате). В них мы будем делать запросы по бесконечному с одной стороны прямоугольнику: $(-\\infty, q_{x}] \\times [q_{y}, q_{y}^{'}]$, если $q_{x} \\lt x_{mid}$, и $[q_{x}, \\infty) \\times [q_{y}, q_{y}^{'}]$ иначе.\n",
+ "\n",
+ "Делая запрос с версией $v$, мы будем обрабатывать все вершины, $x$ координаты которых меньше или равны, чем $v$. Таким образом, чтобы получить все вершины в стакане, достаточно сделать range-запрос по $y$ координате в подходящем дереве."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Теорема\n",
+ "> Дерево интервалов c персистентными деревьями поиска вместо списков занимает $O(n)$ памяти, строится за $O(n \\log n)$, и выдает все отрезки, пересекающиеся с заданным вертикальным, за $O(\\log^{2}n + k)$\n",
+ "\n",
+ "$\\triangleright$
\n",
+ " \n",
+ "В персистентном дереве поиска будем использовать комбинацию метода копирования путей и толстых вершин, которая дает амортизированные $O(1)$ времени и памяти на добавление вершины. \n",
+ "**Память**: каждый $v_{aux}$ занимает $O(n_{aux})$ памяти, $\\sum n_{aux} = n$, значит, $\\sum |v.aux| = O(n)$ \n",
+ "**Время построения**: дерево поиска строится за $O(n \\log n)$, как и 2 отсортированных списка, поэтому\n",
+ "предыдущая оценка работает. \n",
+ "**Время запроса**: запрос в дереве поиска занимает $O(\\log n + k)$, всего таких деревьев $O(\\log n)$. Суммарное время спуска до тех точек, которые нам нужны, будет $O(\\log^{2} n)$ (спуск по дереву интервалов + спуск в персистентном дереве). Суммарное время прохода по поддеревьям, содержащим необходимые точки, равно $O(k)$. Итого мы имеем: $O(\\log^{2} n + k)$.\n",
+ "
$\\triangleleft$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Приоритетное дерево поиска (Priority Search Tree)\n",
+ "\n",
+ "Вместо дерева интервалов можно использовать структуру, которая специально заточена на обработку запросов в виде бесконечных \"стаканов\". Рассмотрим приоритетное дерево поиска.\n",
+ "\n",
+ "Приоритетное дерево поиска изначально было введено [McCreight](http://www.iis.sinica.edu.tw/~dtlee/dtlee/CRCbook_chapter18.pdf). Это гибрид двух структур: бинарное дерево поиска и приоритетная очередь. Приоритетная очередь — очередь, которая поддерживает следующие операции: вставку элемента и удаление минимального (с наивысшим приоритетом) элемента, обозначаемое $delete\\_min$. Обычно $delete\\_min$ занимает константное время, в то время как обновление очереди, сохраняя доступность для чтения минимального элемента, занимает логарифмическое время. Однако, поиск элемента в приоритетной очереди, как правило, занимает линейное время. Для поддержки эффективного поиска, приоритетная очередь улучшена до приоритетного дерева поиска. Мы дадим формальное определение и его алгоритм пострения позже. \n",
+ "\n",
+ "Так как приоритетная очередь представляет множество $S$ элементов, каждый из которых состоит из двух частей: первая — ключ из линейно упорядоченного множества, скажем множества $R$ вещественных чисел, и вторая — приоритет также из линейно упорядоченного множества, то мы можем представить данное множество $S$ как множество точех двумерного пространства. $x$ и $y$ координаты точки $p$ представляют собой ключ и приоритет, соответственно.\n",
+ "\n",
+ "Поскольку у нас нет гарантий того, что во входных данных у всех точек координаты будут различными, мы можем искуственно упорядочить концы отрезков в случае совпадений одной из координат (например, используя индексацию отрезков).\n",
+ "\n",
+ "Приоритетное дерево поиска может быть использовано для поддержки запросов поиска, среди множества $S$, состоящего из $n$ точек, точки $p$ с минимальным $p.y$ таким, что его $x$ координата лежит в заданном промежутке $[l,r]$, то есть $l \\leqslant p.x \\leqslant r$. Как будет показано позже, ответ на данный запрос может быть получен за $O(\\log n)$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Построение приоритетного дерева поиска\n",
+ "\n",
+ "Как и ранее, корневая вершина, $Priority\\_Search\\_Tree\\_root(S)$, представляет целое множество $S$ из точек. Каждая вершина $v$ в дереве будет иметь ключ $v.key$, и вспомогательные данные $v.aux$, содержащие индекс точки и её приоритет, и два указателя $v.left$ и $v.right$ на его левое и правое поддеревья, такие, что все ключи, содержащиеся в левом поддереве меньше, чем $v.key$, и все ключи из правого поддерева — больше. Представленный ниже псевдокод для рекурсивного построения приоритетного дерева поиска из множества $S$, содержащего $n$ точек из $R^2$. Смотрите Рис 2 (а) с примером."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**function** $Priority\\_Search\\_Tree(S)$ \n",
+ "/\\*Возвращает указатель $v$ на корень ($Priority\\_Search\\_Tree\\_root(S)$) приоритетного дерева поиска для множества $S$. \\*/\n",
+ " \n",
+ "**Input:** Множество $S$, состоящее из $n$ точек из $R^2$, $S = \\{p_{i}|i=1,2,...,n\\}$ и каждая точка $p_{i} = (p_{i}.x, p_{i}.y)$, где $p_{i}.x$ и $p_{i}.y$ — это $x$ и $y$ координаты $p_{i}, p_{i}.x, p_{i}.y \\in R, i = 1,2,...,n$.\n",
+ "\n",
+ "**Output:** Приоритетное дерево поиска с корнем, равным $Priority\\_Search\\_Tree\\_root(S)$.\n",
+ "\n",
+ "**Method:**\n",
+ "1. Если $S = \\varnothing$, то завершить работу\n",
+ "2. Создать вершину $v$ такую, что $v.key$ равен медиане множества $\\{p.x|p \\in S\\}$, и $v.aux$ содержит индекс $i$ точки $p_{i}$, чья $y$ координата является минимальной среди всех $y$ множества $S$, то есть $p_{i}.y = min\\{p.y|p \\in S\\}$.\n",
+ "3. Пусть $S_{l} = \\{p \\in S \\backslash \\{p_{v.aux} \\} | p.x \\leqslant x.key \\}$ и $S_{r} = \\{p \\in S \\backslash \\{p_{v.aux} \\} | p.x \\gt x.key \\}$ обозначают множества точек, чьи $x$ координаты меньше или равны и больше, чем $v.key$.\n",
+ "4. $v.left = Priority\\_Search\\_Tree\\_root(S_{l})$.\n",
+ "5. $v.right = Priority\\_Search\\_Tree\\_root(S_{r})$.\n",
+ "6. $return \\space v$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def priority_search_tree(S):\n",
+ " if not S:\n",
+ " return None\n",
+ " v = Node(median([points.x for points in S]))\n",
+ " v.aux = min(S, key=lambda point: point.y)\n",
+ " S.remove(v.aux)\n",
+ " v.left = priority_search_tree(list(filter(lambda p: p.x <= v.key, S)))\n",
+ " v.right = priority_search_tree(list(filter(lambda p: p.x > v.key, S)))\n",
+ " return v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Таким образом, $Priority\\_Search\\_Tree\\_root(S)$ — это куча на минимум по $y$ координате, то есть точка с минимальной $y$ координатой может быть получена за константное время, и сбалансированное динарное дерево поиска по $x$ координате. Неявно корень дерева $v$ ассоциируется с интервалом $[x_{l}, x_{r}]$ охватывающим $x$ координаты всех точек множества $S$. Корень левого поддерева, на который указывет $v.left$, ассоциируется с интервалом $[x_{l}, v.key]$ охватывающим $x$ координаты всех точек множества $S_{l}$ и корень правого поддерева, на который указывет $v.right$, ассоциируется с интервалом $[v.key, x_{r}]$ охватывающим $x$ координаты всех точек множества $S_{r}$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Теорема\n",
+ "> Приоритетное дерево поиска для множества $S$, состоящего из $n$ точек из $R^2$, может быть построено за $O(n\\log n)$, используя $O(n)$ памяти.\n",
+ "\n",
+ "$\\triangleright$\n",
+ "
\n",
+ "**Память**: каждая вершина соответствует одной точке, таким образом необходимо $O(n)$ памяти. \n",
+ "**Время построения**: сортировка по иксу работает $O(n\\log n)$. Шаг алгоритма без учета рекурсивных вызовов работает за $O(n)$. Получаем рекуррентное соотношение: $T(n) = O(n) + 2 T(\\frac{n}{2})$. По мастер-теореме, решение имеет вид: $T(n) = O(n \\log n)$. Итого мы получаем: $O(n \\log n)$.\n",
+ "
\n",
+ "\n",
+ "На рис. 2 показан пример приоритетного дерева поиска для множества точек. Заметьте, что корень дерева хранит $p_{6}$, так как его $y$ координата наименьшая.\n",
+ "\n",
+ "Сейчас мы покажем пример использования приоритетного дерева поиска на примере. Рассмотрим проблему *grounded 2D range search problem* для множества из $n$ точек в плоскости. Для решения *2D search problem* необходимо найти все точки $p \\in S$ такие, что $p.x$ лежит в интервале $[x_{l}, x_{r}]$, $x_{l} \\leqslant x_{r}$ и $p.y$ лежит в интервале $[y_{l}, y_{r}]$, $y_{l} \\leqslant y_{r}$. Если же интервал по $y$ имеет вид $[-\\infty, y_{r}]$, то тогда *2D range* упоминается как *grounded 2D range* или как *1.5D range*, а *2D search problem* как *grounded 2D range search* или *1.5D search problem*.\n",
+ "\n",
+ "**Grounded 2D Range Search Problem** Дано множество $S$ из $n$ точек из плоскости $R^2$. С возможностью препроцессинга, нати подмножество $F$ из точек, чьи $x$ и $y$ координаты лежат в области $[x_{l}, x_{r}] \\times [-\\infty, y_{r}], x_{l}, x_{r}, y_{r} \\in R, x_{l} \\leqslant x_{r}$.\n",
+ "\n",
+ "Предложенный ниже псевдокод решает данную задачу оптимально. Мы знаем, что приоритетное дерево поиска для $S$ было построено функцией $Priority\\_Search\\_Tree(S)$. Ответ будет лежать в переменой $F$ после исполнения $Priority\\_Search\\_Tree\\_Range\\_Search(v, x_{l}, x_{r}, y_{r}, F)$, где $v$ — это $Priority\\_Search\\_Tree\\_root(S)$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**function** $Priority\\_Search\\_Tree\\_Range\\_Search(v, x_{l}, x_{r}, y_{r}, F)$ \n",
+ "/\\* $v$ указывает на корень дерева, $F$ представляет собой множество и изначально имеет значение $nil$\\*/\n",
+ " \n",
+ "**Input:** Множество $S$, состоящее из $n$ точек из $R^2$, содержащееся в приоритетном дереве поиска $Priority\\_Search\\_Tree(S)$, на который указывает $Priority\\_Search\\_Tree\\_root(S)$, и область $[x_{l}, x_{r}] \\times [-\\infty, y_{r}], x_{l}, x_{r}, y_{r} \\in R, x_{l} \\leqslant x_{r}$.\n",
+ "\n",
+ "**Output:** Подмножество $F \\subseteq S$, в котором лежат точки из из заданной области, то есть $F = \\{p \\in S| x_{l} \\leqslant p.x \\leqslant x_{r} \\space и \\space p.y \\leqslant y_{r}\\}$.\n",
+ "\n",
+ "**Method:**\n",
+ "1. Начинаем с корня $v$ поиск первой разделяющей вершины $v_{split}$ такой, что $v_{split}.key$ лежит в интервале $[x_{l}, x_{r}]$.\n",
+ "2. Для каждой вершины $u$ на пути с вершины $v$ до вершины $v_{split}$:\n",
+ "
если точка $p_{u.aux}$ лежит в промежутке $[x_{l}, x_{r}] \\times [\\infty, y_{r}]$, тогда добавить в мнодество $F$ ($p_{u.aux} \\to F$).
\n",
+ "3. Для каждой вершины $u$ на пути к точке $x_{l}$ в левом поддереве $v_{split}$:\n",
+ "
если путь идет влево от $u$, то выполнить $Priority\\_Search\\_Tree\\_1dRange\\_Search(u.right, y_{r}, F)$.
\n",
+ "4. Для каждой вершины $u$ на пути к точке $x_{r}$ в правом поддереве $v_{split}$:\n",
+ "
если путь идет вправо от $u$, то выполнить $Priority\\_Search\\_Tree\\_1dRange\\_Search(u.left, y_{r}, F)$.
\n",
+ " \n",
+ "**function** $Priority\\_Search\\_Tree\\_1dRange\\_Search(v, y_{r}, F)$ \n",
+ "/\\*Положить в $F$ все точки $p_{i}$, чьи $y$ координаты не больше, чем $y_{r}$, где $i=v.aux$*/\n",
+ "\n",
+ "\n",
+ "1. if $v$ is $nil$ return.\n",
+ "2. if $p_{v.aux}.y \\leqslant y_{r}$ then положить его в $F$ ($p_{u.aux} \\to F$)\n",
+ "3.
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def priority_search_tree_1dRange_search(v, y_r, F):\n",
+ " if v is None:\n",
+ " return\n",
+ " if v.aux.y <= y_r:\n",
+ " F.append(v.aux)\n",
+ " priority_search_tree_1dRange_search(v.left, y_r, F)\n",
+ " priority_search_tree_1dRange_search(v.right, y_r, F)\n",
+ "\n",
+ "\n",
+ "def check(v, x_l, x_r, y_r):\n",
+ " return v.aux.y <= y_r and x_l <= v.aux.x <= x_r\n",
+ "\n",
+ "\n",
+ "def priority_search_tree_range_search(v, x_l, x_r, y_r, F):\n",
+ " while not (x_l <= v.key <= x_r):\n",
+ " if check(v, x_l, x_r, y_r):\n",
+ " F.append(v.aux)\n",
+ " if v.key < x_l:\n",
+ " v = v.left\n",
+ " elif v.key > x_r:\n",
+ " v = v.right\n",
+ " if check(v, x_l, x_r, y_r):\n",
+ " F.append(v.aux)\n",
+ "\n",
+ " u = v.left\n",
+ " while u is not None:\n",
+ " if check(u, x_l, x_r, y_r):\n",
+ " F.append(u.aux)\n",
+ " if u.key >= x_l:\n",
+ " u = u.left\n",
+ " priority_search_tree_1dRange_search(u.right, y_r, F)\n",
+ " else:\n",
+ " u = u.right\n",
+ "\n",
+ " u = v.right\n",
+ " while u is not None:\n",
+ " if check(u, x_l, x_r, y_r):\n",
+ " F.append(u.aux)\n",
+ " if u.key <= x_r:\n",
+ " u = u.right\n",
+ " priority_search_tree_1dRange_search(u.left, y_r, F)\n",
+ " else:\n",
+ " u = u.left"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Функция $Priority\\_Search\\_Tree\\_1dRange\\_Search(v, y_{r}, F)$ извлекает все точки, которые хранятся в приоритетном дереве поиска с корнем в $v$, с $y$ координатой меньшей или равной, чем $y_{r}$. Поиск заканивается в вершине $u$, в которой хранимая точка имеет $y$ координату большую, чем $y_{r}$, подразумевая, что все вершины в поддереве с корнем в $u$ удовлетворяют данному свойству. Количество требуемого времени пропорционально выходным данным. Таким образом, мы можем заключить все это в теореме.\n",
+ "\n",
+ "#### Теорема\n",
+ "> **Grounded 2D Range Search Problem** для множества $S$, состоящего из $n$ точек из $R^2$, может быть решена за $O(\\log n)$ (плюс время для вывода), с постройкой приоритетного дерева поиска для $S$ за $O(n\\log n)$ и использованием $O(n)$ памяти.\n",
+ "\n",
+ "$\\triangleright$
\n",
+ "Длина пути в дереве - $O(\\log n)$, а кроме спуска по пути мы только выводим вершины. Итого $O(\\log n + k)$\n",
+ "
$\\triangleleft$"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/3_intersection_of_rectangles/intersection_of_rectangles_slides.ipynb b/3_intersection_of_rectangles/intersection_of_rectangles_slides.ipynb
new file mode 100644
index 0000000..384c57a
--- /dev/null
+++ b/3_intersection_of_rectangles/intersection_of_rectangles_slides.ipynb
@@ -0,0 +1,258 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Постановка задачи\n",
+ "\n",
+ "Дано множество прямоугольников с ребрами, параллельными осям. Дан прямоугольник запроса с таким же свойством. Необходимо найти все прямоугольники из множества, которые пересекаются с прямоугольником запроса ребрами. Утверждается, что нет прямоугольников, чьи концы лежат внутри прямоугольника запроса.\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Сведем задачу к меньшей: \n",
+ "Пусть есть множество таких отрезков, что каждый параллелен какой-либо оси, и прямоугольник запроса. Необходимо ответить, какие отрезки пересекают данный прямоугольник. Известно, что нет отрезка, чей конец лежит внутри прямоугольника, следовательно, отрезок должен пересекать либо две горизонтальные, либо две вертикальные грани. Поэтому достаточно проверить пересечение отрезков с одной горизотальной и с одной вертикальной гранями. Рассмотрим, например, пересечение с вертикальной (с горизонтальной задача решается аналогично). \n",
+ "\n",
+ "Итого мы имеем:\n",
+ "* Множество горизонтальных отрезков\n",
+ "* Один вертикальный отрезок\n",
+ "\n",
+ "И нам необходимо найти подмножество отрезков, пересекающих данный вертикальный.\n",
+ "\n",
+ "Решить данную задачу можно, используя следующие структуры данных:\n",
+ "* Дерево интервалов (interval tree)\n",
+ "* Приоритетное дерево поиска (priority search tree)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Дерево интервалов (interval tree)\n",
+ "\n",
+ "Интервальное дерево, $Interval\\_Tree(S)$, для $S$ — это дополненное бинарное дерево, в котором каждая вершина $v$ имеет следующее строение:\n",
+ "* ключ, $v.key$\n",
+ "* два указателя, $v.left$ и $v.right$, на левое и правое поддеревья\n",
+ "* вспомогательный указатель, $v.aux$, на дополнительную структуру."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Пример\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Построение дерева интервалов\n",
+ "\n",
+ "**function** Interval\\_Tree(S)\n",
+ "\n",
+ "**Pseudocode:**\n",
+ "1. Если $S = \\varnothing$, вернуть $nil$.\n",
+ "2. Создать вершину $v$ такую, что $v.key$ равен $x$, где $x$ — это медиана множества концов отрезков. Определим $S_{l} = \\{I_{i}|r_{i} < x\\}$, $S_{r} = \\{I_{j}|x \\lt l_{j}\\}$ и $S_{m} = \\{I_{k}|r_{k} \\leqslant x \\leqslant r_{k}\\}$, $|S_{l}| \\leqslant \\frac{|S|}{2}$ и $|S_{r}| \\leqslant \\frac{|S|}{2}$.\n",
+ "3. Присвоим $v.left = Interval\\_Tree(S_{l})$\n",
+ "4. Присвоим $v.right = Interval\\_Tree(S_{r})$ \n",
+ "5. Создать вершину $w$, которая является корневой вершиной вспомогательной структуры, ассоциированной с множеством $S_{m}$, такой, что $w.left$ и $w.right$ указывают на два отсортированных массива, $SA(S_{m}.left)$ и $SA(S_{m}.right)$. $SA(S_{m}.left)$ обозначает массив левых концов интервалов из $S_{m}$ в возрастающем порядке, и $SA(S_{m}.right)$ — массив правых концов интервалов из $S_{m}$ в порядке убывания.\n",
+ "6. Множество $v.aux$ приравнять к вершине $w$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Применение\n",
+ "\n",
+ "**Проблема поиска прилежащего интервала (Enclosing Interval Searching Problem)** Даны множество $S$, содержащее $n$ интервалов, и точка $q$. Необходимо найти все интервалы, содержащие $q$, то есть найти подмножество $F \\subseteq S$ такое, что $F = \\{I_{i}|l_{i} \\leqslant q \\leqslant r_{i}\\}$.\n",
+ "\n",
+ "**Проблема поиска перекрывающего интервала (Overlapping Interval Searching Problem)** Даны множество $S$, содержащее $n$ интервалов, и интервал $Q$. Найти все интервалы из $S$, пересекающиеся с $Q$, то есть найти подмножество $F \\subseteq S$ такое, что $F = \\{I_{i}|I_{i} \\cap Q \\neq \\varnothing \\}$.\n",
+ "\n",
+ "#### Теорема\n",
+ "> **Проблема поиска прилежащего интервала (Enclosing Interval Searching Problem)** и **Проблема поиска перекрывающего интервала (Overlapping Interval Searching Problem)** для множества $S$, состоящего из $n$ интервалов, могут быть решены за $O(\\log n)$ (плюс время для вывода), используя $O(n)$ памяти."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Модификация для решения данной задачи\n",
+ "\n",
+ "Вместо двух отсортированных массивов будем хранить два персистентных сбалансированных дерева поиска по $y$ координате (в которых версия дерева соответствует $x$ координате). В них мы будем делать запросы по бесконечному с одной стороны прямоугольнику: $(-\\infty, q_{x}] \\times [q_{y}, q_{y}^{'}]$, если $q_{x} \\lt x_{mid}$, и $[q_{x}, \\infty) \\times [q_{y}, q_{y}^{'}]$ иначе.\n",
+ "\n",
+ "Делая запрос с версией $v$, мы будем обрабатывать все вершины, $x$ координаты которых меньше или равны, чем $v$. Таким образом, чтобы получить все вершины в стакане, достаточно сделать range-запрос по $y$ координате в подходящем дереве.\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "#### Теорема\n",
+ "> Дерево интервалов c персистентными деревьями поиска вместо списков будет занимать $O(n \\log n)$ памяти, построится за $O(n \\log n)$, и будет отвечать на запрос за $O(\\log^{2}n + k)$\n",
+ "\n",
+ "$\\triangleright$
\n",
+ " \n",
+ "В персистентном дереве поиска будем использовать комбинацию метода копирования путей и толстых вершин, которая дает амортизированные $O(1)$ времени и памяти на добавление вершины. \n",
+ "**Память**: каждый $v_{aux}$ занимает $O(n_{aux})$ памяти, $\\sum n_{aux} = n$, значит, $\\sum |v.aux| = O(n)$ \n",
+ "**Время построения**: дерево поиска строится за $O(n \\log n)$, как и 2 отсортированных списка, поэтому\n",
+ "предыдущая оценка работает. \n",
+ "**Время запроса**: запрос в дереве поиска занимает $O(\\log n + k)$, всего таких деревьев $O(\\log n)$. Суммарное время спуска до тех точек, которые нам нужны, будет $O(\\log^{2} n)$ (спуск по дереву интервалов + спуск в персистентном дереве). Суммарное время прохода по поддеревьям, содержащим необходимые точки, равно $O(k)$. Итого мы имеем: $O(\\log^{2} n + k)$.\n",
+ "
$\\triangleleft$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Приоритетное дерево поиска (Priority Search Tree)\n",
+ "\n",
+ "Вместо дерева интервалов можно использовать структуру, которая специально заточена на обработку запросов в виде бесконечных \"стаканов\". Рассмотрим приоритетное дерево поиска.\n",
+ "\n",
+ "Приоритетное дерево поиска изначально было введено [McCreight](http://www.iis.sinica.edu.tw/~dtlee/dtlee/CRCbook_chapter18.pdf). Это гибрид двух структур: бинарное дерево поиска и приоритетная очередь.\n",
+ "\n",
+ "Приоритетное дерево поиска может быть использовано для поддержки запросов поиска, среди множества $S$, состоящего из $n$ точек, точки $p$ с минимальным $p.y$ таким, что его $x$ координата лежит в заданном промежутке $[l,r]$, то есть $l \\leqslant p.x \\leqslant r.$ Как будет показано позже, ответ на данный запрос может быть получен за $O(\\log n).$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Построение приоритетного дерева поиска\n",
+ "\n",
+ "**function** $Priority\\_Search\\_Tree(S)$ \n",
+ "\n",
+ "**Method:**\n",
+ "1. Если $S = \\varnothing$, то завершить работу\n",
+ "2. Создать вершину $v$ такую, что $v.key$ равен среднему значению множества $\\{p.x|p \\in S\\}$, и $v.aux$ содержит индекс $i$ точки $p_{i}$, чья $y$ координата является минимальной среди всех $y$ множества $S$, то есть $p_{i}.y = min\\{p.y|p \\in S\\}$.\n",
+ "3. Пусть $S_{l} = \\{p \\in S \\backslash \\{p_{v.aux} \\} | p.x \\leqslant x.key \\}$ и $S_{r} = \\{p \\in S \\backslash \\{p_{v.aux} \\} | p.x \\gt x.key \\}$ обозначают множества точек, чьи $x$ координаты меньше или равны и больше, чем $v.key$.\n",
+ "4. $v.left = Priority\\_Search\\_Tree\\_root(S_{l})$.\n",
+ "5. $v.right = Priority\\_Search\\_Tree\\_root(S_{r})$.\n",
+ "6. $return \\space v$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "#### Теорема\n",
+ "> Приоритетное дерево поиска для множества $S$, состоящего из $n$ точек из $R^2$, может быть построено за $O(n\\log n)$, используя $O(n)$ памяти.\n",
+ "\n",
+ "$\\triangleright$
\n",
+ "**Память**: каждая вершина соответствует одной точке, таким образом необходимо $O(n)$ памяти. \n",
+ "**Время построения**: сортировка по иксу работает $O(n\\log n)$. Шаг алгоритма без учета рекурсивных вызовов работает за $O(n)$. Получаем рекуррентное соотношение: $T(n) = O(n) + 2 T(\\frac{n}{2})$. По мастер-теореме, решение имеет вид: $T(n) = O(n \\log n)$. Итого мы получаем: $O(n \\log n)$.\n",
+ "
\n",
+ "$\\triangleleft$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Пример\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Применение\n",
+ "\n",
+ "**Grounded 2D Range Search Problem** Дано множество $S$ из $n$ точек из плоскости $R^2$. С возможностью препроцессинга, нати подмножество $F$ из точек, чьи $x$ и $y$ координаты лежат в области $[x_{l}, x_{r}] \\times [-\\infty, y_{r}], x_{l}, x_{r}, y_{r} \\in R, x_{l} \\leqslant x_{r}$.\n",
+ "\n",
+ "#### Теорема\n",
+ "> **Grounded 2D Range Search Problem** для множества $S$, состоящего из $n$ точек из $R^2$, может быть решена за $O(\\log n)$ (плюс время для вывода), с постройкой приоритетного дерева поиска для $S$ за $O(n\\log n)$ и использованием $O(n)$ памяти.\n",
+ "\n",
+ "$\\triangleright$
\n",
+ "Длина пути в дереве - $O(\\log n)$, а кроме спуска по пути мы только выводим вершины. Итого $O(\\log n + k)$\n",
+ "
Дано множество прямоугольников с ребрами, параллельными осям. Дан прямоугольник запроса с таким же свойством. Необходимо найти все прямоугольники из множества, которые пересекаются с прямоугольником запроса ребрами. Утверждается, что нет прямоугольников, чьи концы лежат внутри прямоугольника запроса.
+
+
+
+
+
+
+
+
+
+
Сведем задачу к меньшей:
+Пусть есть множество таких отрезков, что каждый параллелен какой-либо оси, и прямоугольник запроса. Необходимо ответить, какие отрезки пересекают данный прямоугольник. Известно, что нет отрезка, чей конец лежит внутри прямоугольника, следовательно, отрезок должен пересекать либо две горизонтальные, либо две вертикальные грани. Поэтому достаточно проверить пересечение отрезков с одной горизотальной и с одной вертикальной гранями. Рассмотрим, например, пересечение с вертикальной (с горизонтальной задача решается аналогично).
+
Итого мы имеем:
+
+
Множество горизонтальных отрезков
+
Один вертикальный отрезок
+
+
И нам необходимо найти подмножество отрезков, пересекающих данный вертикальный.
+
Решить данную задачу можно, используя следующие структуры данных:
Создать вершину $v$ такую, что $v.key$ равен $x$, где $x$ — это медиана множества концов отрезков. Определим $S_{l} = \{I_{i}|r_{i} < x\}$, $S_{r} = \{I_{j}|x \lt l_{j}\}$ и $S_{m} = \{I_{k}|r_{k} \leqslant x \leqslant r_{k}\}$, $|S_{l}| \leqslant \frac{|S|}{2}$ и $|S_{r}| \leqslant \frac{|S|}{2}$.
+
Присвоим $v.left = Interval\_Tree(S_{l})$
+
Присвоим $v.right = Interval\_Tree(S_{r})$
+
Создать вершину $w$, которая является корневой вершиной вспомогательной структуры, ассоциированной с множеством $S_{m}$, такой, что $w.left$ и $w.right$ указывают на два отсортированных массива, $SA(S_{m}.left)$ и $SA(S_{m}.right)$. $SA(S_{m}.left)$ обозначает массив левых концов интервалов из $S_{m}$ в возрастающем порядке, и $SA(S_{m}.right)$ — массив правых концов интервалов из $S_{m}$ в порядке убывания.
Проблема поиска прилежащего интервала (Enclosing Interval Searching Problem) Даны множество $S$, содержащее $n$ интервалов, и точка $q$. Необходимо найти все интервалы, содержащие $q$, то есть найти подмножество $F \subseteq S$ такое, что $F = \{I_{i}|l_{i} \leqslant q \leqslant r_{i}\}$.
+
Проблема поиска перекрывающего интервала (Overlapping Interval Searching Problem) Даны множество $S$, содержащее $n$ интервалов, и интервал $Q$. Найти все интервалы из $S$, пересекающиеся с $Q$, то есть найти подмножество $F \subseteq S$ такое, что $F = \{I_{i}|I_{i} \cap Q \neq \varnothing \}$.
Проблема поиска прилежащего интервала (Enclosing Interval Searching Problem) и Проблема поиска перекрывающего интервала (Overlapping Interval Searching Problem) для множества $S$, состоящего из $n$ интервалов, могут быть решены за $O(\log n)$ (плюс время для вывода), используя $O(n)$ памяти.
Вместо двух отсортированных массивов будем хранить два персистентных сбалансированных дерева поиска по $y$ координате (в которых версия дерева соответствует $x$ координате). В них мы будем делать запросы по бесконечному с одной стороны прямоугольнику: $(-\infty, q_{x}] \times [q_{y}, q_{y}^{'}]$, если $q_{x} \lt x_{mid}$, и $[q_{x}, \infty) \times [q_{y}, q_{y}^{'}]$ иначе.
+
Делая запрос с версией $v$, мы будем обрабатывать все вершины, $x$ координаты которых меньше или равны, чем $v$. Таким образом, чтобы получить все вершины в стакане, достаточно сделать range-запрос по $y$ координате в подходящем дереве.
Дерево интервалов c персистентными деревьями поиска вместо списков будет занимать $O(n \log n)$ памяти, построится за $O(n \log n)$, и будет отвечать на запрос за $O(\log^{2}n + k)$
+
+
$\triangleright$
+
В персистентном дереве поиска будем использовать комбинацию метода копирования путей и толстых вершин, которая дает амортизированные $O(1)$ времени и памяти на добавление вершины.
+Память: каждый $v_{aux}$ занимает $O(n_{aux})$ памяти, $\sum n_{aux} = n$, значит, $\sum |v.aux| = O(n)$
+Время построения: дерево поиска строится за $O(n \log n)$, как и 2 отсортированных списка, поэтому
+предыдущая оценка работает.
+Время запроса: запрос в дереве поиска занимает $O(\log n + k)$, всего таких деревьев $O(\log n)$. Суммарное время спуска до тех точек, которые нам нужны, будет $O(\log^{2} n)$ (спуск по дереву интервалов + спуск в персистентном дереве). Суммарное время прохода по поддеревьям, содержащим необходимые точки, равно $O(k)$. Итого мы имеем: $O(\log^{2} n + k)$.
+</div>$\triangleleft$
Вместо дерева интервалов можно использовать структуру, которая специально заточена на обработку запросов в виде бесконечных "стаканов". Рассмотрим приоритетное дерево поиска.
+
Приоритетное дерево поиска изначально было введено McCreight. Это гибрид двух структур: бинарное дерево поиска и приоритетная очередь.
+
Приоритетное дерево поиска может быть использовано для поддержки запросов поиска, среди множества $S$, состоящего из $n$ точек, точки $p$ с минимальным $p.y$ таким, что его $x$ координата лежит в заданном промежутке $[l,r]$, то есть $l \leqslant p.x \leqslant r.$ Как будет показано позже, ответ на данный запрос может быть получен за $O(\log n).$
Создать вершину $v$ такую, что $v.key$ равен среднему значению множества $\{p.x|p \in S\}$, и $v.aux$ содержит индекс $i$ точки $p_{i}$, чья $y$ координата является минимальной среди всех $y$ множества $S$, то есть $p_{i}.y = min\{p.y|p \in S\}$.
+
Пусть $S_{l} = \{p \in S \backslash \{p_{v.aux} \} | p.x \leqslant x.key \}$ и $S_{r} = \{p \in S \backslash \{p_{v.aux} \} | p.x \gt x.key \}$ обозначают множества точек, чьи $x$ координаты меньше или равны и больше, чем $v.key$.
Приоритетное дерево поиска для множества $S$, состоящего из $n$ точек из $R^2$, может быть построено за $O(n\log n)$, используя $O(n)$ памяти.
+
+
$\triangleright$
+**Память**: каждая вершина соответствует одной точке, таким образом необходимо $O(n)$ памяти.
+**Время построения**: сортировка по иксу работает $O(n\log n)$. Шаг алгоритма без учета рекурсивных вызовов работает за $O(n)$. Получаем рекуррентное соотношение: $T(n) = O(n) + 2 T(\frac{n}{2})$. По мастер-теореме, решение имеет вид: $T(n) = O(n \log n)$. Итого мы получаем: $O(n \log n)$.
+
Grounded 2D Range Search Problem Дано множество $S$ из $n$ точек из плоскости $R^2$. С возможностью препроцессинга, нати подмножество $F$ из точек, чьи $x$ и $y$ координаты лежат в области $[x_{l}, x_{r}] \times [-\infty, y_{r}], x_{l}, x_{r}, y_{r} \in R, x_{l} \leqslant x_{r}$.
Grounded 2D Range Search Problem для множества $S$, состоящего из $n$ точек из $R^2$, может быть решена за $O(\log n)$ (плюс время для вывода), с постройкой приоритетного дерева поиска для $S$ за $O(n\log n)$ и использованием $O(n)$ памяти.
+
+
$\triangleright$
+Длина пути в дереве - $O(\log n)$, а кроме спуска по пути мы только выводим вершины. Итого $O(\log n + k)$
+