Пересечение прямоугольника с множеством прямоугольников (PST)

Материал из Викиконспекты
Перейти к: навигация, поиск

Определение

Priority Search Tree позволяет решать следующую задачу. Даны [math]n[/math] точек, а также запросы. Каждый запрос характеризуется отрезком по одной координате [math][y_1, y_2][/math], а также числом [math]x_q[/math] по другой координате. Для каждого запроса структура возвращает все точки, которые находятся в отрезке [math][y_1, y_2][/math] по одной координате, и имеют другую координату не больше [math]x_q[/math]. На каждый запрос дерево отвечает за [math]O(\log\,n + k)[/math], где [math]k[/math] — количество точек в ответе.

Как строится?

Дерево строится рекурсивно. Выберем точку с наименьшей [math]x[/math]-координатой (если таких несколько, то из них выберем с наименьшим [math]y[/math]). Все остальные точки отсортируем по [math]y[/math] и разобьем на две равные (количество может отличаться на один) части. Далее будем считать, что все точки первой части имеют координату [math]y[/math] меньше всех точек второй части (с одинаковыми [math]y[/math] поступаем как обычно — сравниваем по [math]x[/math]). Для каждого из двух множеств, если оно не пусто, построим дерево рекурсивно.

Теорема:
PST занимает [math]O(n)[/math] памяти.
Доказательство:
[math]\triangleright[/math]
Каждая точка будет корнем ровно одного дерева. Количество деревьев не превышает количества точек. В сумме [math]O(n)[/math].
[math]\triangleleft[/math]
Теорема:
Максимальная глубина PST [math]O(\log\,n)[/math].
Доказательство:
[math]\triangleright[/math]
При рекурсивном вызове количество точек уменьшается как минимум в два раза.
[math]\triangleleft[/math]
Теорема:
Построение PST работает за [math]O(n\log\,n)[/math] времени.
Доказательство:
[math]\triangleright[/math]
Необходимо отсортировать точки по [math]y[/math] — для этого необходимо [math]O(n\log\,n)[/math]. Построение каждого дерева требует [math]O(k)[/math], где [math]k[/math] — количество точек в дереве, чтобы найти минимальную по [math]x[/math] точку, а также для построения новых списков точек для сыновей. На каждом уровне [math]O(n)[/math] точек, уровней [math]O(\log\,n)[/math], значит, в сумме тоже [math]O(n\log\,n)[/math].
[math]\triangleleft[/math]

Как отвечать на запрос?

Если [math]x[/math] корня больше [math]x_q[/math], то никакие точки дерева не могут быть в ответе. Будем хранить также минимальную и максимальную по [math]y[/math] точки, которые лежат в поддереве. Тогда, если отрезок запроса по [math]y[/math] не пересекается с отрезком по [math]y[/math] всех точек поддерева, то также никакие точки из дерева не должны быть добавлены в ответ. Если корень дерева лежит в нужном интервале, добавим его в ответ. Вызовем рекурсивно поиск ответа для двух сыновей.

Теорема:
Будет получен правильный ответ на запрос.
Доказательство:
[math]\triangleright[/math]
Вроде бы очевидно.
[math]\triangleleft[/math]
Теорема:
Ответ на запрос работает за [math]O(\log\,n+k)[/math], где [math]k[/math] — размер ответа.
Доказательство:
[math]\triangleright[/math]
Тут все сложнее. Представим, что мы ищем [math]y_1[/math] и [math]y_2[/math] (из запроса) в PST как в обычном дереве поиска по [math]y[/math]. Рассмотрим все точки, которые лежат между двумя путями поиска. Среди них рассмотрим точки с [math]x \lt = x_q[/math]. Ровно эти точки и есть ответ. Разобьем все вершины на три группы. Первая — вершины, которые лежат хотя бы на одном из двух путей. Их [math]O(\log\,n)[/math]. Вторая — вершины, которые находятся между двумя путями. Третья — все остальные. Время обработки вершин первой группы не превышает [math]O(\log\,n)[/math]. Как только мы зашли в вершину третей группы, сразу поймем, что отрезок по [math]y[/math] не пересекается с запросом и выйдем. А зайти в них мы можем только из вершин, которые лежат на двух путях (а их [math]O(\log\,n)[/math]). Отлично! Зайдя в вершину второй группы, мы либо ее добавим в ответ и продолжим, либо выйдем, так как единственная причина, почему корень не подошел — его [math]x[/math] слишком большой, а, значит, и все вершины поддерева не подходят. В итоге их обработка занимает [math]O(k)[/math]. В сумме все хорошо!
[math]\triangleleft[/math]

Псевдокод

 struct PST 
   point root
   point min_y, max_y
   PST *down, *up
 // pts sorted by y
 PST * build_PST(vector<point> pts)
   if (pts.size() == 0)
     return NULL
   min_y = max_ y = root = min_element(pts) // by x
   remove_element(pts, root)
   int mid = pts.size() / 2
   vector<point> down_pts = pts[0..mid]
   vector<point> up_pts = pts[mid+1..pts.size() - 1]
   down = build_PST(down_pts)
   up = build_PST(up_pts)
   if (down != NULL)
     min_y = min(min_y, down->min_y)
   if (up != NULL)
     max_y = max(max_y, up->max_y)
 void req(PST * tree, int y1, int y2, int x_max, vector<point> & ans)
   if (tree == NULL)
     return
   if (x_max < tree->root.x)
     return
   if (y1 > tree->max_y || y2 < tree->min_y)
     return
   if (tree->root in [y1..y2]x[-inf;x_max])
     answer.push_back(tree->root)
   req(tree->down, y1, y2, x_max, ans)
   req(tree->up, y1, y2, x_max, ans)

Причем тут пересечение прямоугольника с множеством прямоугольников?

PSTVIEW.png

Задача решается следующим образом. Будем находить ответ тремя разными способами, а их объединение и будет настоящим ответом. Будем считать, что объединение множеств можно делать за [math]O(n)[/math], где [math]n[/math] — суммарное количество элементов в них.

Первый способ. Найдем все прямоугольники, в которых прямоугольник из запроса лежит полностью. Для этого нужно решать такую задачу. Дано множество прямоугольников, для заданной точки найти все прямоугольники, в которых она лежит. Это точно можно решать за [math]O(\log^2\,n+answer)[/math] с помощью двухмерного дерева интервалов. А может можно и проще.

Второй способ. Найдем все прямоугольники, которые полностью лежат внутри заданного в запросе. Для этого воспользуемся range tree. Просто для каждого прямоугольника добавим в range tree его левый верхний угол. Задав запрос в виде нашего прямоугольника, мы получим то, что и нужно.

Третий способ. Найдем все прямоугольники, хотя бы одна сторона которого пересекает заданный в запросе. Для этого воспользуемся деревом отрезков. В структуру добавим все стороны всех прямоугольников и зададим запрос в виде нашего прямоугольника.

Утверждается, что все прямоугольники, которые нам нужны — объединение ответов, полученных тремя способами. Оценивать асимтотику всего этого мне совсем не хочется, но, наверное, это [math]O(\log^2\,n+answer)[/math] времени на запрос. [math]O(n\log\,n)[/math] памяти из-за range tree и [math]O(n\log^2\,n)[/math] на построение. Но, возможно, я что-то забыл или что-то можно сделать более эффективно.