Перейти к содержимому


Выпуклая оболочка Грэхема-Эндрю


В теме одно сообщение

#1 LVP

    Продвинутый пользователь

  • Администраторы
  • 187 сообщений
  • ГородВитебск

Отправлено 14 октября 2020 - 11:00

Выпуклая оболочка Грэхема-Эндрю (от Рубиша Артура)

Одной из наиболее известных задач на геометрию - задача на построение выпуклой оболочки(ссылка на задачу внизу). Кто-то делает его алгоритмом Джарвиса за квадрат, кто-то - обычным алгоритмом Грэхема за n log n, но с использованием вещественных чисел.

Однако всё же есть несколько способов обойтись без ошибки по времени и вещественных чисел. Одним из них является алгоритм Грэхема-Эндрю. Алгоритм является оптимальным и использует только сложение, умножение и вычитание.

Суть алгоритма довольно несложная, если вы хорошо понимаете алгоритм Грэхема. Давайте найдём самую левую нижнюю и самую правую верхнюю точки из данных нам. Пусть это будут точки А и В соответственно. Дальше мы проведём прямую через эти точки. Так, мы разделили прямую на две части - верхнюю и нижнюю(Точки на этой прямой, кроме А и В, мы не учитываем ни в какую из частей. Сами же точки А и В войдут в обе части).Дальше мы сортируем все точки по абсциссе, а при их равенстве - по ординате. Всё, что нам осталось сделать - использовать алгоритм Грэхема для построения сначала верхней, а затем и нижней части оболочки.

Код(для задачи, указанной ниже).

#include <bits/stdc++.h>
#define ld long double
#define int long long
#define pb push_back
#define F first
#define S second
#define endl '\n'
using namespace std;
int32_t main ()
{
    ///Алгоритм Грэхема-Эндрю, написан и объяснён _GreatArthur_
    cin.tie(0);
    cout.tie(0);
    ios_base::sync_with_stdio(0);
    int n;
    cin >> n;
    int copy = n;
    long double x[n], y[n];
    ///Используем map для удаления одинаковых точек.
    map < pair < ld, ld >, int > mp;
    for (int i = 0; i < n; i++)
    {
        cin >> x[i] >> y[i];
        mp[{x[i], y[i]}]++;
        if (mp[{x[i], y[i]}] > 1)
        {
            i--;
            n--;
        }
    }
    ///Отдельно проверяем частный случай.
    if (n == 1)
    {
        cout << 1 << endl << x[0] << ' ' << y[0];
        return 0;
    }
    ///Находим номера точек А и В.
    int numA = 0, numB = 0;
    for (int i = 0; i < n; i++)
    {
        if (x[numA] > x[i] || (x[numA] == x[i] && y[numA] > y[i]))
        numA = i;
        if (x[numB] < x[i] || (x[numB] == x[i] && y[numB] < y[i]))
        numB = i;
    }
    ///Создаём два множества для верхней и нижней части оболочки.
    vector < int > up, down;
    up.pb(numA);
    down.pb(numA);
    ///Сортируем точки по абсциссе(v[i].F), а при их равенстве - по ординате(v[i].S.F). Чтобы понять номер точек после сортировки, также храним их номер(v[i].S.S).
    vector < pair < int, pair < int, int > > > v;
    for (int i = 0; i < n; i++)
        v.pb({x[i], {y[i], i}});
    sort(v.begin(), v.end());
    int ii = 0;
    ///Ищем верхнюю часть оболочки.
    do
    {
        ii++;
        if ((x[v[ii].S.S] - x[numA]) * (y[numB] - y[numA]) - (y[v[ii].S.S] - y[numA]) * (x[numB] - x[numA]) <= 0 && v[ii].S.S != numB) continue;
        ///Для каждой точки мы проверяем, сверху ли она или её номер - numB.Если да, то мы делаем алгоритм ниже, иначе - просто игнорируем её.
        while (up.size() > 1 && (x[v[ii].S.S] - x[up[up.size() - 2]]) * (y[up[up.size() - 1]] - y[up[up.size() - 2]]) - (y[v[ii].S.S] - y[up[up.size() - 2]]) * (x[up[up.size() - 1]] - x[up[up.size() - 2]]) >= 0)
            up.pop_back();
        up.pb(v[ii].S.S);
    }
    while (ii < v.size() - 1);
    ii = 0;
    ///Ищем нижнюю часть оболочки.
    do
    {
        ii++;
        if ((x[v[ii].S.S] - x[numA]) * (y[numB] - y[numA]) - (y[v[ii].S.S] - y[numA]) * (x[numB] - x[numA]) >= 0 && v[ii].S.S != numB) continue;
        ///Для каждой точки мы проверяем, снизу ли она или её номер - numB.Если да, то мы делаем алгоритм ниже, иначе - просто игнорируем её.
        while (down.size() > 1 && (x[v[ii].S.S] - x[down[down.size() - 2]]) * (y[down[down.size() - 1]] - y[down[down.size() - 2]]) - (y[v[ii].S.S] - y[down[down.size() - 2]]) * (x[down[down.size() - 1]] - x[down[down.size() - 2]]) <= 0)
            down.pop_back();
        down.pb(v[ii].S.S);
    }
    while (ii < v.size() - 1);
    ///Выводим оболочку.
    cout << up.size() + down.size() - 2 << endl;
    if (copy % 2 == 0)
    {
        for (int i = 0; i < up.size(); i++)
            cout << x[up[i]] << ' ' << y[up[i]] << endl;
        for (int i = down.size() - 2; i >= 1; i--)
            cout << x[down[i]] << ' ' << y[down[i]] << endl;
    }
    else
    {
        for (int i = 1;i <= down.size() - 2; i++)
            cout << x[down[i]] << ' ' << y[down[i]] << endl;
        for (int i = up.size() - 1;i >= 0;i--)
            cout << x[up[i]] << ' ' << y[up[i]] << endl;
    }
    return 0;
}


Сдать задачу:

https://informatics....hapterid=1877#1

Сообщение отредактировал LVP: 17 октября 2020 - 08:04

Виктория Павловна

#2 Степа

    Новичок

  • Пользователи
  • Pip
  • 1 сообщений
  • ГородВитебск

Отправлено 17 октября 2020 - 06:59

Рубиш Артур проносит свои глубочайшие извинения и просит в поиске точек a и b искать левую нижнюю и правую верхнюю точки.





Количество пользователей, читающих эту тему: 1

0 пользователей, 1 гостей, 0 анононимных