Перейти до основного вмісту

Мангеттенська відстань

Означення​

Для точок pp і qq на площині ми можемо визначити відстань між ними як суму різниць їхніх координат xx та yy:

d(p,q)=∣xp−xq∣+∣yp−yq∣d(p,q) = |x_p - x_q| + |y_p - y_q|

Визначена в такий спосіб, відстань відповідає так званій мангеттенській (таксомоторній) геометрії, у якій точки вважаються перехрестями в добре спланованому місті на кшталт Мангеттена, де рухатися можна лише вулицями — горизонтально або вертикально, як показано на зображенні нижче:

Manhattan Distance

Це зображення показує кілька найкоротших шляхів від однієї чорної точки до іншої, і всі вони мають довжину 1212.

Із цією відстанню можна виконувати деякі цікаві трюки та алгоритми, і кілька з них ми покажемо тут.

Коли підходить цей алгоритм?
  • Відстані у вашій задачі вимірюються як ∣xp−xq∣+∣yp−yq∣|x_p - x_q| + |y_p - y_q| (рух лише вздовж осей), а не евклідово? (якщо потрібна евклідова найближча пара → Пошук найближчої пари точок)
  • Вам потрібен один із трюків саме для L1L_1-метрики: найвіддаленіша пара, перетворення в метрику Чебишова чи мангеттенське MST?
  • Для MST: ваги ребер — це мангеттенські відстані між точками, і вам вистачить O(nlog⁡n)O(n \log n) кандидатних ребер з наступним алгоритмом Краскала?

Найвіддаленіша пара точок у мангеттенській відстані​

Маємо nn точок PP і хочемо знайти пару точок p,qp,q, які найбільш віддалені одна від одної, тобто максимізувати ∣xp−xq∣+∣yp−yq∣|x_p - x_q| + |y_p - y_q|.

Поміркуймо спершу в одному вимірі, тож y=0y=0. Головне спостереження полягає в тому, що ми можемо перебрати, чи дорівнює ∣xp−xq∣|x_p - x_q| значенню xp−xqx_p - x_q, чи −xp+xq-x_p + x_q, адже якщо ми «промахнемося зі знаком» абсолютної величини, то отримаємо лише менше значення, тому це не може вплинути на відповідь. Більш формально виконується:

∣xp−xq∣=max⁡(xp−xq,−xp+xq)|x_p - x_q| = \max(x_p - x_q, -x_p + x_q)

Тож, наприклад, ми можемо спробувати взяти таке pp, що xpx_p має знак плюс, і тоді qq повинно мати від'ємний знак. У такий спосіб ми хочемо знайти:

max⁡p,q∈P(xp+(−xq))=max⁡p∈P(xp)+max⁡q∈P(−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 ).

Зауважимо, що цю ідею можна поширити далі на 2 (або більше!) вимірів. Для dd вимірів нам потрібно перебрати 2d2^d можливих значень знаків. Наприклад, якщо ми у 22 вимірах і перебираємо варіант, коли pp має обидва знаки плюс, ми хочемо знайти:

max⁡p,q∈P[(xp+(−xq))+(yp+(−yq))]=max⁡p∈P(xp+yp)+max⁡q∈P(−xq−yq).\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).

Оскільки ми зробили pp і qq незалежними, тепер легко знайти pp і qq, які максимізують цей вираз.

Код нижче узагальнює це на dd вимірів і працює за O(n⋅2d⋅d)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);
}

Поворот точок і відстань Чебишова​

Добре відомо, що для всіх m,n∈Rm, n \in \mathbb{R},

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

Щоб довести це, нам достатньо проаналізувати знаки mm і nn. Це залишаємо як вправу.

Ми можемо застосувати цю рівність до формули мангеттенської відстані й з'ясувати, що

d((x1,y1),(x2,y2))=∣x1−x2∣+∣y1−y2∣=max(∣(x1+y1)−(x2+y2)∣,∣(y1−x1)−(y2−x2)∣).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)|).

Останній вираз у попередній рівності — це відстань Чебишова точок (x1+y1,y1−x1)(x_1 + y_1, y_1 - x_1) і (x2+y2,y2−x2)(x_2 + y_2, y_2 - x_2). Це означає, що після застосування перетворення

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

мангеттенська відстань між точками pp і qq перетворюється на відстань Чебишова між α(p)\alpha(p) і α(q)\alpha(q).

Також ми можемо помітити, що α\alpha — це спіральна подібність (поворот площини з наступним розтягуванням відносно центра OO) з центром (0,0)(0, 0), кутом повороту 45∘45^{\circ} за годинниковою стрілкою та розтягуванням у 2\sqrt{2} разів.

Ось зображення, яке допоможе уявити це перетворення:

Chebyshev transformation

Мангеттенське мінімальне кістякове дерево​

Задача про мангеттенське MST полягає в тому, щоб для заданих точок на площині знайти ребра, які з'єднують усі точки й мають мінімальну сумарну вагу. Вага ребра, що з'єднує дві точки, — це мангеттенська відстань між ними. Для простоти вважаємо, що всі точки розташовані в різних місцях. Тут ми показуємо спосіб знайти MST за O(nlog⁡n)O(n \log{n}), відшукуючи для кожної точки її найближчого сусіда в кожному октанті, як показано на зображенні нижче. Це дасть нам O(n)O(n) кандидатних ребер, які, як ми покажемо нижче, гарантовано містять MST. Останній крок — застосувати якийсь стандартний алгоритм пошуку MST, наприклад, алгоритм Краскала з використанням системи неперетинних множин.

8 octants picture 8 октантів відносно точки S

Наведений тут алгоритм уперше було представлено в статті H. Zhou, N. Shenoy, and W. Nichollos (2002). Існує також інший відомий алгоритм, який використовує підхід «розділяй і володарюй» від J. Stolfi, і він теж дуже цікавий та відрізняється лише способом пошуку найближчого сусіда в кожному октанті. Обидва мають однакову складність, але наведений тут простіше реалізувати, і він має менший константний множник.

Спершу зрозуміймо, чому достатньо розглядати лише найближчого сусіда в кожному октанті. Ідея полягає в тому, щоб показати, що для точки ss і будь-яких двох інших точок pp і qq в тому самому октанті виконується d(p,q)<max⁡(d(s,p),d(s,q))d(p, q) < \max(d(s, p), d(s, q)). Це важливо, бо показує, що якби існувало MST, у якому ss з'єднана й з pp, і з qq, ми могли б видалити одне з цих ребер і додати ребро (p,q)(p,q), що зменшило б сумарну вартість. Щоб довести це, без втрати загальності припустимо, що pp і qq лежать в октанті R1R_1, який визначається умовами: xs≤xx_s \leq x і xs−ys>x−yx_s - y_s > x - y, а далі розглянемо кілька випадків. Зображення нижче дає певну інтуїцію щодо того, чому це правда.

unique nearest neighbor Інтуїтивно, обмеженість октанта робить неможливим, щоб pp і qq були обидва ближчі до ss, ніж одна до одної

Отже, головне питання — як знайти найближчого сусіда в кожному октанті для кожної з nn точок.

Найближчий сусід у кожному октанті за O(n log n)​

Для простоти зосередимося на октанті NNE (R1R_1 на зображенні вище). Усі інші напрямки можна знайти тим самим алгоритмом, повертаючи вхідні дані.

Ми застосуємо підхід із замітальною прямою. Ми обробляємо точки з південного заходу на північний схід, тобто за неспадним x+yx + y. Ми також тримаємо множину точок, які ще не мають свого найближчого сусіда, і називаємо її «активною множиною». Нижче ми додаємо зображення, які допоможуть уявити алгоритм.

manhattan-mst-sweep Чорним зі стрілкою показано напрямок замітальної прямої. Усі точки нижче цієї лінії — в активній множині, а точки вище ще не оброблено. Зеленим позначено точки, які лежать в октанті обробленої точки. Червоним — точки, які не лежать у шуканому октанті.
manhattan-mst-sweep На цьому зображенні ми бачимо активну множину після обробки точки pp. Зауважте, що 22 зелені точки з попереднього зображення мали pp у своєму північно-північно-східному октанті й більше не належать до активної множини, бо вже знайшли свого найближчого сусіда.

Коли ми додаємо нову точку pp, для кожної точки ss, яка має її у своєму октанті, ми можемо безпечно призначити pp найближчим сусідом. Це правда, бо їхня відстань дорівнює d(p,s)=∣xp−xs∣+∣yp−ys∣=(xp+yp)−(xs+ys)d(p,s) = |x_p - x_s| + |y_p - y_s| = (x_p + y_p) - (x_s + y_s), оскільки pp лежить у північно-північно-східному октанті. Оскільки всі наступні точки не матимуть меншого значення x+yx + y через крок сортування, pp гарантовано має найменшу відстань. Тоді ми можемо вилучити всі такі точки з активної множини, і нарешті додати pp до активної множини.

Наступне питання — як ефективно знайти, які точки ss мають pp у своєму північно-північно-східному октанті. Тобто які точки ss задовольняють:

  • xs≤xpx_s \leq x_p
  • xp−yp<xs−ysx_p - y_p < x_s - y_s

Оскільки жодна точка в активній множині не лежить в області R1R_1 іншої, ми також маємо, що для двох точок q1q_1 і q2q_2 в активній множині xq1≠xq2x_{q_1} \neq x_{q_2}, і їхнє впорядкування означає xq1<xq2  ⟹  xq1−yq1≤xq2−yq2x_{q_1} < x_{q_2} \implies x_{q_1} - y_{q_1} \leq x_{q_2} - y_{q_2}.

Ви можете спробувати уявити це на зображеннях вище, думаючи про впорядкування за x−yx - y як про «замітальну пряму», яка йде з північного заходу на південний схід, тобто перпендикулярно до намальованої.

Це означає, що якщо ми тримаємо активну множину впорядкованою за xx, кандидати ss розташовані поспіль. Тоді ми можемо знайти найбільший xs≤xpx_s \leq x_p і обробляти точки в порядку спадання xx, доки не порушиться друга умова xp−yp<xs−ysx_p - y_p < x_s - y_s (насправді ми можемо дозволити, щоб xp−yp=xs−ysx_p - y_p = x_s - y_s, і це опрацьовує випадок точок з однаковими координатами). Зауважте, що оскільки ми вилучаємо з множини одразу після обробки, це даватиме амортизовану складність O(nlog⁡(n))O(n \log(n)). Тепер, коли ми маємо найближчу точку в північно-східному напрямку, ми повертаємо точки й повторюємо. Можна показати, що насправді в такий спосіб ми також знаходимо найближчу точку в південно-західному напрямку, тож можна повторювати лише 4 рази замість 8.

Підсумовуючи, ми:

  • Сортуємо точки за x+yx + y у неспадному порядку;
  • Для кожної точки ми проходимо активну множину, починаючи з точки з найбільшим xx таким, що x≤xpx \leq x_p, і перериваємо цикл, якщо xp−yp≥xs−ysx_p - y_p \geq x_s - y_s. Для кожної допустимої точки ss ми додаємо ребро (s,p,d(s,p))(s,p, d(s,p)) до нашого списку;
  • Додаємо точку pp до активної множини;
  • Повертаємо точки й повторюємо, доки не пройдемо всі октанти.
  • Застосовуємо алгоритм Краскала до списку ребер, щоб отримати MST.

Нижче ви можете знайти реалізацію, засновану на тій, що з KACTL.

struct point {
long long x, y;
};

// Повертає список ребер у форматі (вага, u, v).
// Передача цього списку до алгоритму Краскала дасть мангеттенське MST.
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++) { // для кожного повороту
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) { // поворот
if (rot & 1) p.x *= -1;
else swap(p.x, p.y);
}
}
return edges;
}

Задачі​