Skip to Content

Distancia de Manhattan

Definición

Para puntos pp y qq en un plano, podemos definir la distancia entre ellos como la suma de las diferencias entre sus coordenadas xx e yy:

d(p,q)=xpxq+ypyqd(p,q) = |x_p - x_q| + |y_p - y_q|

Definida de esta manera, la distancia corresponde a la llamada geometría de Manhattan (taxicab) , en la que los puntos se consideran intersecciones en una ciudad bien diseñada, como Manhattan, donde solo se puede mover por las calles en horizontal o en vertical, como se muestra en la imagen de abajo:

Distancia de Manhattan

Estas imágenes muestran algunos de los caminos más cortos de un punto negro al otro, todos ellos de longitud 1212.

Hay algunos trucos y algoritmos interesantes que se pueden hacer con esta distancia, y mostraremos algunos de ellos aquí.

Par de puntos más lejanos en distancia de Manhattan

Dados nn puntos PP, queremos encontrar el par de puntos p,qp,q que están más alejados, es decir, maximizar xpxq+ypyq|x_p - x_q| + |y_p - y_q|.

Pensemos primero en una dimensión, de modo que y=0y=0. La observación principal es que podemos hacer fuerza bruta sobre si xpxq|x_p - x_q| es igual a xpxqx_p - x_q o a xp+xq-x_p + x_q, porque si “erramos el signo” del valor absoluto, solo obtendremos un valor más pequeño, de modo que no puede afectar la respuesta. Más formalmente, se cumple que:

xpxq=max(xpxq,xp+xq)|x_p - x_q| = \max(x_p - x_q, -x_p + x_q)

Así, por ejemplo, podemos intentar que pp sea tal que xpx_p tenga el signo más, y entonces qq debe tener el signo menos. De este modo queremos encontrar:

maxp,qP(xp+(xq))=maxpP(xp)+maxqP(xq).\max\limits_{p, q \in P}(x_p + (-x_q)) = \max\limits_{p \in P}(x_p) + \max\limits_{q \in P}( - x_q ).

Nótese que podemos extender esta idea más allá a 2 (¡o más!) dimensiones. Para dd dimensiones, debemos hacer fuerza bruta sobre 2d2^d valores posibles de los signos. Por ejemplo, si estamos en 22 dimensiones y forzamos que pp tenga ambos signos más, queremos encontrar:

maxp,qP[(xp+(xq))+(yp+(yq))]=maxpP(xp+yp)+maxqP(xqyq).\max\limits_{p, q \in P} [(x_p + (-x_q)) + (y_p + (-y_q))] = \max\limits_{p \in P}(x_p + y_p) + \max\limits_{q \in P}(-x_q - y_q).

Como hicimos independientes a pp y qq, ahora es fácil encontrar el pp y el qq que maximizan la expresión.

El código de abajo generaliza esto a dd dimensiones y corre en O(n2dd)O(n \cdot 2^d \cdot d).

long long ans = 0; for (int msk = 0; msk < (1 << d); msk++) { long long mx = LLONG_MIN, mn = LLONG_MAX; for (int i = 0; i < n; i++) { long long cur = 0; for (int j = 0; j < d; j++) { if (msk & (1 << j)) cur += p[i][j]; else cur -= p[i][j]; } mx = max(mx, cur); mn = min(mn, cur); } ans = max(ans, mx - mn); }

Rotación de los puntos y distancia de Chebyshev

Es bien sabido que, para todo m,nRm, n \in \mathbb{R},

m+n=max(m+n,mn).|m| + |n| = \text{max}(|m + n|, |m - n|).

Para demostrarlo, solo hay que analizar los signos de mm y nn. Y se deja como ejercicio.

Podemos aplicar esta ecuación a la fórmula de la distancia de Manhattan para descubrir que

d((x1,y1),(x2,y2))=x1x2+y1y2=max((x1+y1)(x2+y2),(y1x1)(y2x2)).d((x_1, y_1), (x_2, y_2)) = |x_1 - x_2| + |y_1 - y_2| = \text{max}(|(x_1 + y_1) - (x_2 + y_2)|, |(y_1 - x_1) - (y_2 - x_2)|).

La última expresión de la ecuación anterior es la distancia de Chebyshev  de los puntos (x1+y1,y1x1)(x_1 + y_1, y_1 - x_1) y (x2+y2,y2x2)(x_2 + y_2, y_2 - x_2). Esto significa que, después de aplicar la transformación

α:(x,y)(x+y,yx),\alpha : (x, y) \to (x + y, y - x),

la distancia de Manhattan entre los puntos pp y qq se convierte en la distancia de Chebyshev entre α(p)\alpha(p) y α(q)\alpha(q).

Además, podemos darnos cuenta de que α\alpha es una semejanza espiral  (rotación del plano seguida de una dilatación respecto de un centro OO) con centro (0,0)(0, 0), ángulo de rotación de 4545^{\circ} en sentido horario y dilatación por 2\sqrt{2}.

Aquí hay una imagen para ayudar a visualizar la transformación:

Transformación de Chebyshev

Árbol de expansión mínima de Manhattan

El problema del MST de Manhattan consiste en, dados algunos puntos en el plano, encontrar las aristas que conectan todos los puntos y tienen una suma total mínima de pesos. El peso de una arista que conecta dos puntos es su distancia de Manhattan. Por simplicidad, asumimos que todos los puntos tienen ubicaciones distintas. Aquí mostramos una forma de encontrar el MST en O(nlogn)O(n \log{n}) hallando para cada punto su vecino más cercano en cada octante, como se representa en la imagen de abajo. Esto nos dará O(n)O(n) aristas candidatas, que, como mostramos más abajo, garantizarán que contienen el MST. El paso final es entonces usar algún MST estándar, por ejemplo, el algoritmo de Kruskal usando conjuntos disjuntos .

imagen de 8 octantes *Los 8 octantes relativos a un punto S*

El algoritmo mostrado aquí se presentó por primera vez en un artículo de H. Zhou, N. Shenoy y W. Nichollos (2002) . También hay otro algoritmo conocido que usa un enfoque de divide y vencerás de J. Stolfi , que también es muy interesante y solo difiere en la forma en que encuentran el vecino más cercano en cada octante. Ambos tienen la misma complejidad, pero el que se presenta aquí es más fácil de implementar y tiene un factor constante menor.

Primero, entendamos por qué basta considerar solo el vecino más cercano en cada octante. La idea es mostrar que para un punto ss y cualesquiera otros dos puntos pp y qq en el mismo octante, d(p,q)<max(d(s,p),d(s,q))d(p, q) < \max(d(s, p), d(s, q)). Esto es importante, porque muestra que si hubiera un MST donde ss está conectado tanto a pp como a qq, podríamos borrar una de estas aristas y añadir la arista (p,q)(p,q), lo que disminuiría el costo total. Para demostrarlo, asumimos sin pérdida de generalidad que pp y qq están en el octante R1R_1, que se define por: xsxx_s \leq x y xsys>xyx_s - y_s > x - y, y luego hacemos un análisis por casos. La imagen de abajo da algo de intuición sobre por qué esto es cierto.

vecino más cercano único *Intuitivamente, la limitación del octante hace imposible que pp y qq estén ambos más cerca de ss que el uno del otro*

Por tanto, la pregunta principal es cómo encontrar el vecino más cercano en cada octante para cada uno de los nn puntos.

Vecino más cercano en cada octante en O(n log n)

Por simplicidad nos concentramos en el octante NNE (R1R_1 en la imagen de arriba). Todas las demás direcciones se pueden encontrar con el mismo algoritmo rotando la entrada.

Usaremos un enfoque de línea de barrido. Procesamos los puntos de suroeste a noreste, es decir, por x+yx + y no decreciente. También mantenemos un conjunto de puntos que aún no tienen su vecino más cercano, al que llamamos “conjunto activo”. Añadimos las imágenes de abajo para ayudar a visualizar el algoritmo.

barrido-mst-manhattan *En negro con una flecha se puede ver la dirección de la línea de barrido. Todos los puntos por debajo de esta línea están en el conjunto activo, y los puntos de arriba aún no están procesados. En verde vemos los puntos que están en el octante del punto procesado. En rojo los puntos que no están en el octante buscado.*
barrido-mst-manhattan *En esta imagen vemos el conjunto activo después de procesar el punto pp. Nótese que los 22 puntos verdes de la imagen anterior tenían a pp en su octante norte-noreste y ya no están en el conjunto activo, porque ya encontraron su vecino más cercano.*

Cuando añadimos un punto nuevo pp, para cada punto ss que lo tiene en su octante podemos asignar de forma segura pp como el vecino más cercano. Esto es cierto porque su distancia es d(p,s)=xpxs+ypys=(xp+yp)(xs+ys)d(p,s) = |x_p - x_s| + |y_p - y_s| = (x_p + y_p) - (x_s + y_s), porque pp está en el octante norte-noreste. Como todos los puntos siguientes no tendrán un valor menor de x+yx + y por el paso de ordenamiento, está garantizado que pp tiene la menor distancia. Luego podemos quitar todos esos puntos del conjunto activo, y finalmente añadir pp al conjunto activo.

La siguiente pregunta es cómo encontrar de forma eficiente qué puntos ss tienen a pp en el octante norte-noreste. Es decir, qué puntos ss satisfacen:

  • xsxpx_s \leq x_p
  • xpyp<xsysx_p - y_p < x_s - y_s

Como ningún punto del conjunto activo está en la región R1R_1 de otro, también tenemos que para dos puntos q1q_1 y q2q_2 del conjunto activo, xq1xq2x_{q_1} \neq x_{q_2} y su orden implica xq1<xq2    xq1yq1xq2yq2x_{q_1} < x_{q_2} \implies x_{q_1} - y_{q_1} \leq x_{q_2} - y_{q_2}.

Se puede intentar visualizar esto en las imágenes de arriba pensando en el orden de xyx - y como una “línea de barrido” que va del noroeste al sureste, de modo que es perpendicular a la que está dibujada.

Esto significa que si mantenemos el conjunto activo ordenado por xx los candidatos ss están colocados de forma consecutiva. Entonces podemos encontrar el mayor xsxpx_s \leq x_p y procesar los puntos en orden decreciente de xx hasta que se rompa la segunda condición xpyp<xsysx_p - y_p < x_s - y_s (en realidad podemos permitir que xpyp=xsysx_p - y_p = x_s - y_s y eso trata el caso de puntos con coordenadas iguales). Nótese que, como quitamos del conjunto justo después de procesar, esto tendrá una complejidad amortizada de O(nlog(n))O(n \log(n)). Ahora que tenemos el punto más cercano en la dirección noreste, rotamos los puntos y repetimos. Es posible mostrar que de esta forma también encontramos el punto más cercano en la dirección suroeste, de modo que podemos repetir solo 4 veces, en lugar de 8.

En resumen:

  • Ordenamos los puntos por x+yx + y en orden no decreciente;
  • Para cada punto, iteramos sobre el conjunto activo empezando por el punto con el mayor xx tal que xxpx \leq x_p, y rompemos el bucle si xpypxsysx_p - y_p \geq x_s - y_s. Para cada punto válido ss añadimos la arista (s,p,d(s,p))(s,p, d(s,p)) a nuestra lista;
  • Añadimos el punto pp al conjunto activo;
  • Rotamos los puntos y repetimos hasta iterar sobre todos los octantes.
  • Aplicamos el algoritmo de Kruskal sobre la lista de aristas para obtener el MST.

Abajo se puede encontrar una implementación, basada en la de KACTL .

struct point { long long x, y; }; // Devuelve una lista de aristas en el formato (weight, u, v). // Pasar esta lista al algoritmo de Kruskal da el MST de Manhattan. vector<tuple<long long, int, int>> manhattan_mst_edges(vector<point> ps) { vector<int> ids(ps.size()); iota(ids.begin(), ids.end(), 0); vector<tuple<long long, int, int>> edges; for (int rot = 0; rot < 4; rot++) { // para cada rotación sort(ids.begin(), ids.end(), [&](int i, int j){ return (ps[i].x + ps[i].y) < (ps[j].x + ps[j].y); }); map<int, int, greater<int>> active; // (xs, id) for (auto i : ids) { for (auto it = active.lower_bound(ps[i].x); it != active.end(); active.erase(it++)) { int j = it->second; if (ps[i].x - ps[i].y > ps[j].x - ps[j].y) break; assert(ps[i].x >= ps[j].x && ps[i].y >= ps[j].y); edges.push_back({(ps[i].x - ps[j].x) + (ps[i].y - ps[j].y), i, j}); } active[ps[i].x] = i; } for (auto &p : ps) { // rotar if (rot & 1) p.x *= -1; else swap(p.x, p.y); } } return edges; }

Problemas