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

Сума Мінковського опуклих многокутників

Означення

Розглянемо дві множини точок AA і BB на площині. Сума Мінковського A+BA + B означається як {a+baA,bB}\{a + b| a \in A, b \in B\}. Тут ми розглядатимемо випадок, коли AA і BB складаються з опуклих многокутників PP і QQ разом з їхніми внутрішніми областями. Упродовж цієї статті ми ототожнюватимемо многокутники з упорядкованими послідовностями їхніх вершин, щоб позначення на кшталт P|P| чи PiP_i мали сенс. Виявляється, що сума опуклих многокутників PP і QQ є опуклим многокутником, який має щонайбільше P+Q|P| + |Q| вершин.

Коли підходить цей алгоритм?
  • Обидва доданки — це саме опуклі многокутники (а не довільні множини точок чи неопуклі фігури)?
  • Вам справді потрібна поточкова сума {a+b}\{a + b\} — наприклад, для перевірки колізій рухомих опуклих тіл чи редукції відстані між многокутниками до відстані до точки?
  • Вершини многокутників задано в порядку обходу (або їх можна впорядкувати), щоб скористатися злиттям ребер за полярним кутом за O(P+Q)O(|P| + |Q|)?

Алгоритм

Тут ми вважаємо, що вершини многокутників пронумеровані циклічно, тобто PP=P0, QQ=Q0P_{|P|} = P_0,\ Q_{|Q|} = Q_0 і так далі.

Оскільки розмір суми лінійно залежить від розмірів початкових многокутників, ми маємо прагнути знайти алгоритм лінійного часу. Припустимо, що обидва многокутники впорядковані проти годинникової стрілки. Розглянемо послідовності ребер {PiPi+1}\{\overrightarrow{P_iP_{i+1}}\} і {QjQj+1}\{\overrightarrow{Q_jQ_{j+1}}\}, упорядковані за полярним кутом. Ми стверджуємо, що послідовність ребер P+QP + Q можна отримати, злиявши ці дві послідовності зі збереженням порядку за полярним кутом і замінивши послідовні співнапрямлені вектори на їхню суму. Прямолінійне застосування цієї ідеї дає алгоритм лінійного часу, проте відновлення вершин P+QP + Q з послідовності сторін потребує багаторазового додавання векторів, що може спричинити небажані проблеми з точністю, якщо ми працюємо з координатами у форматі з рухомою комою, тому ми опишемо невелику модифікацію цієї ідеї.

Спершу ми маємо переупорядкувати вершини так, щоб перша вершина кожного многокутника мала найменшу y-координату (якщо таких вершин декілька, оберемо ту, що має найменшу x-координату). Після цього сторони обох многокутників стануть відсортованими за полярним кутом, тож сортувати їх вручну не потрібно. Тепер ми створюємо два вказівники ii (вказує на вершину PP) і jj (вказує на вершину QQ), обидва спочатку дорівнюють 0. Ми повторюємо наступні кроки, доки i<Pi < |P| або j<Qj < |Q|.

  1. Додаємо Pi+QjP_i + Q_j до P+QP + Q.

  2. Порівнюємо полярні кути PiPi+1\overrightarrow{P_iP_{i + 1}} і QjQj+1\overrightarrow{Q_jQ_{j+1}}.

  3. Збільшуємо вказівник, який відповідає меншому куту (якщо кути рівні, збільшуємо обидва).

Візуалізація

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

Visual

Відстань між двома многокутниками

Одне з найпоширеніших застосувань суми Мінковського — обчислення відстані між двома опуклими многокутниками (або просто перевірка того, чи вони перетинаються). Відстань між двома опуклими многокутниками PP і QQ означається як minaP,bQab\min\limits_{a \in P, b \in Q} ||a - b||. Можна зауважити, що відстань завжди досягається між двома вершинами або вершиною та ребром, тож ми можемо легко знайти відстань за O(PQ)O(|P||Q|). Проте завдяки кмітливому використанню суми Мінковського ми можемо зменшити складність до O(P+Q)O(|P| + |Q|).

Якщо ми відобразимо QQ відносно точки (0,0)(0, 0), отримавши многокутник Q-Q, задача зводиться до знаходження найменшої відстані між точкою з P+(Q)P + (-Q) і (0,0)(0, 0). Ми можемо знайти цю відстань за лінійний час за допомогою наступної ідеї. Якщо (0,0)(0, 0) лежить усередині многокутника або на його межі, відстань дорівнює 00, інакше відстань досягається між (0,0)(0, 0) і деякою вершиною або ребром многокутника. Оскільки суму Мінковського можна обчислити за лінійний час, ми отримуємо алгоритм лінійного часу для знаходження відстані між двома опуклими многокутниками.

Реалізація

Нижче наведено реалізацію суми Мінковського для многокутників з цілочисловими точками. Зауважте, що в цьому випадку всі обчислення можна виконувати в цілих числах, оскільки замість обчислення полярних кутів і безпосереднього їх порівняння ми можемо дивитися на знак векторного добутку двох векторів.

struct pt{
long long x, y;
pt operator + (const pt & p) const {
return pt{x + p.x, y + p.y};
}
pt operator - (const pt & p) const {
return pt{x - p.x, y - p.y};
}
long long cross(const pt & p) const {
return x * p.y - y * p.x;
}
};

void reorder_polygon(vector<pt> & P){
size_t pos = 0;
for(size_t i = 1; i < P.size(); i++){
if(P[i].y < P[pos].y || (P[i].y == P[pos].y && P[i].x < P[pos].x))
pos = i;
}
rotate(P.begin(), P.begin() + pos, P.end());
}

vector<pt> minkowski(vector<pt> P, vector<pt> Q){
// перша вершина має бути найнижчою
reorder_polygon(P);
reorder_polygon(Q);
// ми маємо забезпечити циклічну індексацію
P.push_back(P[0]);
P.push_back(P[1]);
Q.push_back(Q[0]);
Q.push_back(Q[1]);
// основна частина
vector<pt> result;
size_t i = 0, j = 0;
while(i < P.size() - 2 || j < Q.size() - 2){
result.push_back(P[i] + Q[j]);
auto cross = (P[i + 1] - P[i]).cross(Q[j + 1] - Q[j]);
if(cross >= 0 && i < P.size() - 2)
++i;
if(cross <= 0 && j < Q.size() - 2)
++j;
}
return result;
}

Задачі