Coding Test/BaekJoon_C++

백준 1708 <볼록 껍질> C++

JunOnJuly 2024. 4. 21. 00:35
728x90

https://www.acmicpc.net/problem/1708

 

1708번: 볼록 껍질

첫째 줄에 점의 개수 N(3 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에 걸쳐 각 점의 x좌표와 y좌표가 빈 칸을 사이에 두고 주어진다. 주어지는 모든 점의 좌표는 다르다. x좌표와 y좌표의 범

www.acmicpc.net


볼록 껍질, 즉 Convex Hull 문제입니다.

이 문제를 푸는 방법 중 보편적으로 알려져 있는 알고리즘은 그라함 스캔, 그라함 알고리즘으로 알려져 있습니다. 해당 알고리즘으로 문제를 풀어보겠습니다.

 

그라함 스캔을 사용하기 위해서는 CCW 알고리즘을 알아야 하는데, 외적을 이용하는 알고리즘으로 세 개의 점의 상태를 알 수 있습니다. 즉 세 개의 점이 하나의 직선 위에 있는지 혹은 두 점을 이은 직선과 나머지 한점의 위치관계를 알 수 있습니다.

 

우선 점을 x 축 기준으로 정렬합니다. 혹은 y 축으로 정렬해도 상관 없습니다. x 축 혹은 y 축에서 가장 크거나 가장 작은 점은 무조건 볼록 껍질에 포함되기 때문에 시작점을 찾고 다음 과정을 위해 최적화하는 과정이라고 생각하면 됩니다.

 

다음은 정렬된 점들 중 가장 앞점을 기준으로 나머지 점들을 시계방향 혹은 반시계방향으로 정렬해야 합니다. 아래 코드에서는 시계방향으로 정했습니다. 다만 한 방향으로 정한 뒤 다음의 과정에서도 일관되게 사용하면 어느 방향으로 정렬해도 상관 없습니다.

 

정렬의 과정에서 CCW 알고리즘을 사용합니다. 기준점과 두 점을 순서대로 비교하는데 만약 세 점이 직선상에 있다면 두 점 중 기준점과 가까운 점이 앞서게 되고 기준점-점1-점2 를 기준으로 시계방향 회전이라면 점 1 이 앞서게 됩니다.

 

다음은 그라함 스캔 알고리즘을 사용하게 되는데 우선 스택에 정렬된 점들 중 [0], [1] 점을 삽입합니다. 그 뒤로 한 점씩 스택에 삽입하며 진행됩니다. 스택[top-2] - 스택[top-1] - 스택[top] 을 기준으로 시계방향이라면 스택에 다음 점을 추가해주고 반시계 방향이거나 직선이라면 스택[top-1] 을 스택에서 제거줍니다.


#include <iostream>
#include <array>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>
#include <set>

using namespace std;

// 점 구조체
struct Point {
    // x y 좌표
    long long int x;
    long long int y;
    // 기준 점
    array<long long int, 2> origin_point;
};

// ccw
long long int ccw(Point& point_1, Point& point_2, Point& point_3) {
    // 임시 어레이
    array<Point, 4> point_arr = { point_1, point_2, point_3, point_1 };
    // 계산
    long long int ccw_sum = 0;
    for (int i = 0; i < 3; i++) {
        ccw_sum += point_arr[i].x * point_arr[i + 1].y;
        ccw_sum -= point_arr[i].y * point_arr[i + 1].x;
    }
    return ccw_sum;
}

// x 기준 오름차순으로 정렬
bool sort_point(Point& point_1, Point& point_2) {
    // 우선 x 기준
    if (point_1.x < point_2.x) return true;
    // x 가 같으면 y 기준
    else if (point_1.x == point_2.x and point_1.y < point_2.y) return true;
    // 나머지
    else return false;
}

// 시계 방향으로 정렬
bool sort_clock_wise(Point& point_1, Point& point_2) {
    // 기준 점
    Point origin_point;
    origin_point.x = point_1.origin_point[0];
    origin_point.y = point_1.origin_point[1];
    // ccw
    long long int ccw_points = ccw(origin_point, point_1, point_2);
    // origin_point / point_1, origin_point / point_2 거리 비교
    long long int dist_o1 = pow((origin_point.y - point_1.y), 2) + pow((origin_point.x - point_1.x), 2);
    long long int dist_o2 = pow((origin_point.y - point_2.y), 2) + pow((origin_point.x - point_2.x), 2);
    // 음수면 시계방향
    if (ccw_points < 0) return true;
    // 0 이면 직선방향, origin_point 와 가까우면 앞으로
    else if (ccw_points == 0 and dist_o1 < dist_o2) return true;
    // 양수면 반시계
    else return false;
}

// 그라함 스캔
vector<Point> graham_scan(vector<Point>& points) {
    // 스택
    vector<Point> stk(points.begin(), points.begin() + 3);
    // 인덱스
    long long int idx = 3;
    // 순회
    while (true) {
        // 포인트
        Point point_1 = stk[stk.size() - 2];
        Point point_2 = stk[stk.size() - 1];
        // 기준 포인트 설정
        point_1.origin_point = { stk[stk.size() - 3].x, stk[stk.size() - 3].y };
        point_2.origin_point = { stk[stk.size() - 3].x, stk[stk.size() - 3].y };
        // 세 점이 반시계방향으로 정렬되어있으면
        if (!sort_clock_wise(point_1, point_2)) {
            // 중간점 삭제
            stk.erase(stk.end() - 2);
        }
        // 시계방향이거나 직선상에 있으면
        else {
            Point point_3 = stk[stk.size() - 3];
            // 직선상에 있으면
            if (ccw(point_3, point_1, point_2) == 0) {
                // 중간점 삭제
                stk.erase(stk.end() - 2);
            }
            // 시계방향이면
            else {
                // 모두 순회하면 끝
                if (idx >= points.size()) break;
                // 스택에 다음 점 추가
                stk.push_back(points[idx]);
                // 인덱스 최신화
                idx += 1;
            }
        }
        // 스택의 크기가 2 이하면
        if (stk.size() <= 2) {
            // 모두 순회하면 끝
            if (idx >= points.size()) break;
            // 스택에 다음 점 추가
            stk.push_back(points[idx]);
            // 인덱스 최신화
            idx += 1;
        }
    }

    return stk;
}

int main(void) {
    // 입력
    long long int N;
    cin >> N;
    // 점을 저장
    vector<Point> points;
    for (int i = 0; i < N; i++) {
        Point point;
        cin >> point.x >> point.y;
        points.push_back(point);
    }
    // 점을 x 기준, y 기준 으로 정렬
    sort(points.begin(), points.end(), sort_point);

    // 기준점 초기화
    for (int i = 0; i < N; i++) {
        points[i].origin_point = { points[0].x, points[0].y };
    }
    // 점을 시계방향으로 정렬
    sort(points.begin(), points.end(), sort_clock_wise);

    /*
    cout << "[ ";
    for (auto pt : points) {
        cout << "[ " << pt.x << ", " << pt.y << " ], ";
    }
    cout << "]\n";
    */

    // 그라함 스캔
    vector<Point> convex_hull = graham_scan(points);

    /*
    cout << "[ ";
    for (auto pt : convex_hull) {
        cout << "[ " << pt.x << ", " << pt.y << " ], ";
    }
    cout << "]";
    */

    cout << convex_hull.size();
}

 

728x90