--- tags: geometry, sweep-line --- # Sweep line ## Definición Es una técnica de geometría computacional que usa el concepto de una línea que va barriendo (haciendo *sweep*) todo el plano y se va deteniendo en algunos lugares de interés. Usualmente la línea es vertical y se va a ir moviendo desde $x=-\infty$ hasta $x=\infty$. En términos prácticos no la podemos mover de forma continua ya que hay infinitos puntos, por lo que solo la vamos a mover de forma discreta en los lugares de interés, a los que llamaremos *eventos*. Lo mínimo que esos eventos necesitan guardar es el tiempo en el que ocurren, y la forma usual de procesarlos es ordenarlos por dicho tiempo de menor a mayor, iterar por todos ellos y mantener cierta información global relativa al problema que irá cambiando entre evento y evento. ## Problemas ### [Intersección de segmentos alineados a los ejes](https://omegaup.com/arena/problem/interseccionsegmentos/#problems) Tenemos $n$ segmentos en el plano ($1 \leq n \leq 10^5$), cada uno dado por sus puntos de inicio $a_i$ y fin $b_i$ para $1 \leq i \leq n$ y $a_i \neq b_i$. Estos segmentos solo pueden ser horizontales o verticales: es decir, si $a_i=(x_1,y_1)$ y $b_i=(x_2,y_2)$ se cumple que $x_1=x_2$ (vertical) o $y_1=y_2$ (horizontal). El problema es hallar el número total de intersecciones entre todos los segmentos. Se garantiza que todas las intersecciones existentes siempre serán entre un segmento horizontal y otro vertical. La forma cuadrática de resolverlo es intentar intersectar cada par de segmentos, y si sí hay intersección, sumar 1 a la respuesta. Para resolverlo usando sweep line vamos a mover una línea vertical infinita desde la izquierda que va a ir tocando los segmentos. Definiremos tres tipos de eventos: - Evento tipo $\texttt{a}$: ocurrirá cuando la línea toque el extremo izquierdo de un segmento horizontal. Se puede ver como el momento en el que la línea *entra* al segmento. - Evento tipo $\texttt{c}$: ocurrirá cuando la línea toque el extremo derecho de un segmento horizontal. Se puede ver como el momento en el que la línea *sale* del segmento. - Evento tipo $\texttt{b}$: ocurrirá cuando la línea coincida con un segmento vertical. Los tres tipos de evento necesitan guardar en qué tiempo ocurren, o sea, en qué coordenada en $x$ la línea los toca. Además, cada evento va a almacenar su segmento asociado. Por lo tanto, por cada segmento horizontal vamos a crear dos eventos $\texttt{a}$ y $\texttt{c}$, y por cada segmento vertical un evento $\texttt{b}$. Ahora viene la parte de procesamiento: ya que ordenamos todos los eventos por la coordenada en $x$ en la que ocurren; llevaremos una estructura de datos global que almacene los **segmentos activos**, o sea, los segmentos **horizontales** que la línea está tocando actualmente. Al principio la estructur estará vacía. Luego, hacemos lo siguiente por cada evento: - Si el evento es de tipo $\texttt{a}$ introducimos su segmento horizontal asociado. - Si el evento es de tipo $\texttt{c}$ borramos su segmento horizontal asociado. - Los eventos de tipo $\texttt{b}$ son los que van a incrementar el número de intersecciones: la idea es ver con cuántos segmentos horizontales activos se cruza el segmento vertical asociado. Supongamos que el segmento vertical tiene rango $y_1 \leq y \leq y_2$, entonces vamos hacer un *range query* en nuestra estructura de datos que nos diga cuántos segmentos horizontales tienen su $y$ entre $y_1$ y $y_2$, e incrementamos la respuesta. Se puede ver que la complejidad dependerá de qué tan eficiente sea nuestra estructura de datos con las queries. Si hacemos las inserciones y eliminaciones de forma bruta, la complejidad sigue siendo $O(n^2)$; pero si usamos un *order statistic set* (estructura que permite inserciones, eliminaciones y queries de lower bound en tiempo logarítmico), obtenemos una complejidad total de $O(n \log n)$. **Preguntas**: - ¿Cómo ordeno dos eventos que ocurren al mismo tiempo? Implementación: ```cpp= #include <bits/stdc++.h> #include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/tree_policy.hpp> using namespace std; using namespace __gnu_pbds; using lli = long long int; struct segment{ // if segment is horizontal, [start, end] is the range in "x", and axis is the common value of "y" // if segment is vertical, [start, end] is the range in "y", and axis is the common value of "x" int start, end, axis; segment(): start(0), end(0), axis(0) {} segment(int start, int end, int axis): start(start), end(end), axis(axis) {} // lexicographic order by (axis, start, end) bool operator<(const segment& s) const{ if(axis != s.axis) return axis < s.axis; if(start != s.start) return start < s.start; return end < s.end; } }; struct event{ int p; // position in "x" when this event char t; // type of event: a (horizontal segment starts), b (vertical segment matches with sweeping line), c (horizontal segment ends) segment s; // associated segment to this event event(): p(0), t(0){} event(int p, char t, segment s): p(p), t(t), s(s) {} // If events occur at the same time, sort by type of event. Otherwise, sort by the time when they occur. bool operator<(const event& e) const{ if(p == e.p) return t < e.t; return p < e.p; } }; typedef tree<segment, null_type, less<segment>, rb_tree_tag, tree_order_statistics_node_update> ordered_set; int main(){ ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; int xi, yi, xf, yf; vector<event> events; for(int i = 0; i < n; ++i){ cin >> xi >> yi >> xf >> yf; if(yi == yf){ // horizontal segment if(xi > xf) swap(xi, xf); // ensure that starting point is on the left events.emplace_back(xi, 'a', segment(xi, xf, yi)); //start events.emplace_back(xf, 'c', segment(xi, xf, yi)); //end }else if(xi == xf){ // vertical segment if(yi > yf) swap(yi, yf); // ensume that starting point is on the bottom events.emplace_back(xi, 'b', segment(yi, yf, xi)); //vertical line }else{ // no deberia ocurrir } } sort(events.begin(), events.end()); ordered_set active; lli cnt = 0; for(const event& e : events){ if(e.t == 'a') active.insert(e.s); else if(e.t == 'c') active.erase(e.s); else if(e.t == 'b'){ // count hoy many active horizontal segments have height in range [e.s.start, e.s.end] --> // e.s.start <= y <= e.s.end --> y <= e.s.end , y >= e.s.start cnt += active.order_of_key(segment(0, 0, e.s.end + 1)) - active.order_of_key(segment(0, 0, e.s.start)); } } cout << cnt << "\n"; return 0; } ``` [**Ejemplo interactivo**](https://www.geogebra.org/classic/m837afvt) ### Par de puntos más cercanos ### Área de unión de rectángulos ## Referencias - https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/ - https://www.cs.mcgill.ca/~cs251/ClosestPair/proofbox.html -