이 포스팅은 Algorithm Problem Solving 시리즈 113 편 중 52 번째 글 입니다.

  • Part 1 - 백준(1003번): 피보나치 함수
  • Part 2 - 백준(1010번): 다리놓기
  • Part 3 - 백준(1012번): 유기농 배추
  • Part 4 - 백준(10159번): 저울
  • Part 5 - 백준(10164번): 격자상의 경로
  • Part 6 - 백준(1018번): 체스판 다시 칠하기
  • Part 7 - 백준(1018번): 체스판 다시 칠하기
  • Part 8 - 백준(1022번): 소용돌이 예쁘게 출력하기
  • Part 9 - 백준(10775번): 공항
  • Part 10 - 백준(10816번): 숫자 카드2
  • Part 11 - 백준(10816번): 숫자카드 2
  • Part 12 - 백준(10819번): 차이를 최대로
  • Part 13 - 백준(10830번): 행렬 곱셈
  • Part 14 - 백준(10844번): 쉬운 계단수
  • Part 15 - 백준(10868번): 최소값
  • Part 16 - 백준(1092번): 배
  • Part 17 - 백준(11003번): 최솟값 찾기
  • Part 18 - 백준(1102번): 발전소
  • Part 19 - 백준(11048번): 이동하기
  • Part 20 - 백준(11053번): 가장 긴 증가하는 부분 수열
  • Part 21 - 백준(11054번): 가장 긴 바이토닉 부분 수열
  • Part 22 - 백준(11055번): 가장 긴 증가하는 부분 수열2
  • Part 23 - 백준(11057번): 오르막 수
  • Part 24 - 백준(1120번): 문자열
  • Part 25 - 백준(11403번): 경로 찾기
  • Part 26 - 백준(11404번): 플루이드
  • Part 27 - 백준(1149번): RGB 거리
  • Part 28 - 백준(11559번): puyo puyo
  • Part 29 - 백준(11655번): ROT13
  • Part 30 - 백준(1167번): 트리의 지름
  • Part 31 - 백준(11722번): 가장 감소하는 부분 수열
  • Part 32 - 백준(12015번): 가장 긴 증가하는 부분 수열(LIS) 2
  • Part 33 - 백준(12851번): 숨바꼭질2
  • Part 34 - 백준(12852번): 1로 만들기 2
  • Part 35 - 백준(12865번): 평범한 배낭
  • Part 36 - 백준(1300번): K번째 수
  • Part 37 - 백준(1325번): 효율적인 해킹
  • Part 38 - 백준(13549번): 숨바꼭질3
  • Part 39 - 백준(13913번): 숨바꼭질4
  • Part 40 - 백준(14002번): 가장 긴 증가하는 부분 수열 4
  • Part 41 - 백준(1431번): 시리얼 넘버
  • Part 42 - 백준(1436번): 영화감독 숌
  • Part 43 - 백준(14499번): 주사위 굴리기
  • Part 44 - 백준(14888번): 연산자 끼워넣기
  • Part 45 - 백준(14889번): 스타트와 링크
  • Part 46 - 백준(14891번): 톱니바퀴
  • Part 47 - 백준(15658번): 연산자 끼워넣기 2
  • Part 48 - 백준(15686번): 치킨 배달
  • Part 49 - 백준(1697번): 숨바꼭질
  • Part 50 - 백준(1697번): 숨바꼭질
  • Part 51 - 백준(1707번): 이분 그래프(Bipartite Graph)
  • Part 52 - This Post
  • Part 53 - 백준(17136번): 색종이 붙이기
  • Part 54 - 백준(1717번): 집합의 표현
  • Part 55 - 백준(17298번): 오큰수
  • Part 56 - 백준(17626번): Four Squares
  • Part 57 - 백준(18870번): 좌표 압축
  • Part 58 - 백준(1890번): 점프
  • Part 59 - 백준(1918번): 후위 표기식
  • Part 60 - 백준(1929번): 소수 구하기
  • Part 61 - 백준(1963번): 소수 경로
  • Part 62 - 백준(1965번): 상자넣기
  • Part 63 - 백준(1976번): 여행가자
  • Part 64 - 백준(1987번): 알파벳
  • Part 65 - 백준(1992번): 쿼드트리
  • Part 66 - 백준(2003번): 수들의 합2
  • Part 67 - 백준(20040번): 사이클 게임
  • Part 68 - 백준(2042번): 구간 합 구하기
  • Part 69 - 백준(2108번): 통계학
  • Part 70 - 백준(2110번): 공유기 설치
  • Part 71 - 백준(2156번): 포도주 시식
  • Part 72 - 백준(2193번): 이친수
  • Part 73 - 백준(2231번): 분해합
  • Part 74 - 백준(2250번): 트리의 높이와 너비
  • Part 75 - 백준(2293번): 동전 1
  • Part 76 - 백준(2294번): 동전 2
  • Part 77 - 백준(2343번): 기타 레슨
  • Part 78 - 백준(2468번): 안전 영역
  • Part 79 - 백준(2512번): 예산
  • Part 80 - 백준(2529번): 부등호
  • Part 81 - 백준(2565번): 전깃줄
  • Part 82 - 백준(2581번): 소수
  • Part 83 - 백준(2583번): 영역구하기
  • Part 84 - 백준(2630번): 색종이 만들기
  • Part 85 - 백준(2644번): 촌수계산
  • Part 86 - 백준(2667번): 단지번호붙이기
  • Part 87 - 백준(2751번): 수 정렬하기 2
  • Part 88 - 백준(2798번): 블랙잭
  • Part 89 - 백준(2904번): 수학은 너무 쉬워
  • Part 90 - 백준(2986번): 파스칼
  • Part 91 - 백준(3015번): 오아시스 재결합
  • Part 92 - 백준(3190번): 뱀
  • Part 93 - 백준(3190번): 벽 부수고 이동하기
  • Part 94 - 백준(4195번): 친구 네트워크
  • Part 95 - 백준(4948번): 베르트랑 공준
  • Part 96 - 백준(4963번): 섬의 개수
  • Part 97 - 백준(4991번): 로봇 청소기
  • Part 98 - 백준(5373번): 큐빙
  • Part 99 - 백준(5437번): 불
  • Part 100 - 백준(6171번): 땅따먹기
  • Part 101 - 백준(6603번): 로또
  • Part 102 - 백준(6603번): 로또
  • Part 103 - 백준(7562번): 나이트의 이동
  • Part 104 - 백준(7568번): 덩치
  • Part 105 - 백준(7576번): 토마토
  • Part 106 - 백준(7579번): 앱
  • Part 107 - 백준(7785번): 회사에 있는 사람
  • Part 108 - 백준(9012번): 괄호
  • Part 109 - 백준(9020번): 골드바흐의 추측
  • Part 110 - 백준(9184번): 신나는 함수 실행
  • Part 111 - 백준(9251번): LCS
  • Part 112 - 백준(9421번): 소수상근수
  • Part 113 - 프로그래머스: 가장 큰 정사각형 찾기
▼ 목록 보기

골드1 : 기하 문제이다.

생각

볼록 껍질, Convex Hull이라 한다. 이 알고리즘에서 유명한 것을 그라함 스캔 알고리즘인데, 해당 동영상을 봐보자.

이것과 같은 알고리즘을 구현하기 위해서는 다음과 같은 절차를 거쳐야 한다.

  1. 가장 y가 작은 점을 구한다.
  2. 그 점을 기준으로 직선의 각을 기준으로 정렬한다.
  3. 각이 가장 작은 점부터 조사하면서 볼록 껍질인지 아닌지 확인하고 추가한다.

이 때, 각을 기준으로 정렬을 수행해야 하는데, 각은 double 형이라 이를 정렬하는데 좋지 않다. 일단 구하기도 어렵고 같은 선상에 있을 때 골치가 아프다..

그래서 이 때 각을 대변해 줄 수 있는 다른 지표로 외적의 부호를 사용한다. 이 부분이 달달한 부분인데, 외적의 값은 x, y축을 기저로 보았을 때, 시계 방향, 반시계 방향을 대변해 준다. 이 때, 특정 두 점과의 외적을 수행하면 서로의 상대적 위치를 알 수 있다.

image

이 연산을 점들을 각 순서로 정렬하는 비교 연산으로 사용하자.

Code

#include<iostream>
#include<vector>
#include<string>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
struct Point{
    ll x, y;
};
vector<Point> p;
int N;

ll ccw(Point p1, Point p2, Point p3){
    return p1.x*p2.y + p2.x*p3.y + p3.x*p1.y - (p2.x*p1.y + p3.x*p2.y + p1.x*p3.y);
}

bool compareMinelement(Point p1, Point p2){
    if (p1.y == p2.y) return p1.x < p2.x;
    else return p1.y < p2.y;
}

bool compareCCW(Point p1, Point p2){
    ll cp = ccw(p[0], p1, p2);
    if (cp == 0) return (p1.x + p1.y) < (p2.x + p2.y);
    return cp > 0;
}



int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
    cin >> N;
    p.resize(N);

    for (int i = 0; i < N; i++) {
        cin >> p[i].x >> p[i].y;
    }

    sort(p.begin(), p.end(), compareMinelement);
    sort(p.begin()+1, p.end(), compareCCW);

    vector<Point> v;
    v.push_back(p[0]);
    v.push_back(p[1]);

    // 이 for문은 세점 중 가장 바깥쪽 점을 의미
    for (int i = 2; i < N; i++) {
        // 볼록을 찾을 때까지 계속 진행
        while (v.size() >= 2) {
            // 두개를 본다.
            Point p2 = v.back();
            v.pop_back();
            Point p1 = v.back();
            // ccw이면 중간에 있는 점(p2)를 확인된 점으로 판단하고 스택에 넣는다.
            if (ccw(p1, p2, p[i]) > 0) {
                v.push_back(p2);
                break;
            }
            // ccw가 아니면 p2를 추가하지 말고 p2이전의 2점과 현재 p[i]와 ccw인지 비교한다. (처음으로 돌아간다)
        }
        // while문을 통과했다면 점이 추가가 된 것이므로 현재 탐색하는 가장 바깥쪽 점도 넣어준다.
        v.push_back(p[i]);
    }
    cout << v.size() <<  '\n';
}

보다 깔끔한 Convex Hull Code

#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
struct Point {
   ll x, y;
   Point(ll a, ll b) :x(a), y(b) {};
   Point() {};
   bool operator<(const Point &rhs) const {
      if (x != rhs.x) return x < rhs.x;
      return y < rhs.y;
   }
};
vector<Point> point;

ll ccw(Point pt1, Point pt2, Point pt3) {
   ll ret = pt1.x*pt2.y + pt2.x*pt3.y + pt3.x*pt1.y;
   ret -= (pt2.x*pt1.y + pt3.x*pt2.y + pt1.x*pt3.y);
   return ret;
}
ll dist(Point pt1, Point pt2) {
   ll dx = pt2.x - pt1.x;
   ll dy = pt2.y - pt1.y;
   return dx * dx + dy * dy;
}

int main(){
    int N;
    cin >> N;
    point.resize(N);
    for (int i = 0; i < N; i++) {
        cin >> point[i].x >> point[i].y;
    }
    vector<Point> hull;
    swap(point[0], *min_element(point.begin(), point.end()));
    sort(point.begin() + 1, point.end(), [](Point x, Point y) {
       ll cw = ccw(point[0], x, y);
       if (cw == 0) return dist(point[0], x) < dist(point[0], y);
       return cw > 0;
    });

    for (auto i : point) {
        // hull의 뒤에서 2번째 값, 1번째 값, 그리고 point의 3번째 값을 비교하여
        // 반시계가 아니면 hull의 맨 뒤의 점을 뺀다.
        // 반시계이면 해당 점을 포함한 상태로 다음점을 비교한다.
       while (hull.size() >= 2 && ccw(hull[hull.size() - 2], hull.back(), i) <= 0) {
          hull.pop_back();
       }
       hull.push_back(i);
    }
    cout << hull.size() << endl;
}

Reference

백준(1708번) - 볼록 껍질