백준 1708번

볼록 껍질

Posted by ChoiCube84 on May 19, 2024 · 12 mins read

백준 1708번

오늘 풀어본 문제는 백준의 1708번 문제1이다. 문제 풀이에 사용한 언어는 C++ 이다.

solved.ac 기준 CLASS

CLASS 6

문제 정보

이 문제의 내용과 조건은 다음과 같다.

문제

다각형의 임의의 두 꼭짓점을 연결하는 선분이 항상 다각형 내부에 존재하는 다각형을 볼록 다각형이라고 한다. 아래 그림에서 (a)는 볼록 다각형이며, (b)는 볼록 다각형이 아니다.

figure 1

조금만 생각해 보면 다각형의 모든 내각이 180180 도 이하일 때 볼록 다각형이 된다는 것을 알 수 있다. 편의상 이 문제에서는 180180 도 미만인 경우만을 볼록 다각형으로 한정하도록 한다.

22 차원 평면에 NN 개의 점이 주어졌을 때, 이들 중 몇 개의 점을 골라 볼록 다각형을 만드는데, 나머지 모든 점을 내부에 포함하도록 할 수 있다. 이를 볼록 껍질 (CONVEX HULL) 이라 한다. 아래 그림은 N=10N=10 인 경우의 한 예이다.

figure 2

점의 집합이 주어졌을 때, 볼록 껍질을 이루는 점의 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 점의 개수 NN (3N100,000)(3 \le N \le 100,000) 이 주어진다. 둘째 줄부터 NN 개의 줄에 걸쳐 각 점의 xx 좌표와 yy 좌표가 빈 칸을 사이에 두고 주어진다. 주어지는 모든 점의 좌표는 다르다. xx 좌표와 yy 좌표의 범위는 절댓값 40,00040,000 을 넘지 않는다. 입력으로 주어지는 다각형의 모든 점이 일직선을 이루는 경우는 없다.

출력

첫째 줄에 볼록 껍질을 이루는 점의 개수를 출력한다.

볼록 껍질의 변에 점이 여러 개 있는 경우에는 가장 양 끝 점만 개수에 포함한다.

풀이과정

1번째 시도

이름에서부터 이 문제가 Convex Hull 알고리즘을 사용하는 문제라는 것을 알 수 있었다.

정확한 알고리즘의 동작 원리와 구현법에 대한 설명은 생략하고, 문제를 해결하기 위해 사용한 방식은 다음과 같다.

  1. 입력 받은 점들을 xx 좌표의 값이 가장 작은 점을 기준으로 CCW 알고리즘을 이용하여 반시계 방향으로 정렬한다.

  2. std::stack 을 활용하여 Convex Hull 알고리즘을 구현하고, 최종적으로 해당 스택에 Convex Hull 을 구성하는 점들을 저장하게 한다.

  3. 스택의 크기를 출력한다.

코드는 다음과 같이 작성하였다. 제출할 때는 헤더파일의 코드를 붙여넣어 제출하였다.

geometric_line.hpp

#ifndef __GEOMETRIC_LINE_HPP__
#define __GEOMETRIC_LINE_HPP__

#include <utility>

template <typename T>
class GeometricLine {
private:
	std::pair<T, T> start;
	std::pair<T, T> end;
public:
	GeometricLine() : start(std::make_pair(0, 0)), end(std::make_pair(0, 0)) {}

	GeometricLine(const std::pair<T, T>& start, const std::pair<T, T>& end) : start(start), end(end) {}

	GeometricLine& operator=(const GeometricLine& other) {
		this->start.first = other.start.first;
		this->start.second = other.start.second;
		this->end.first = other.end.first;
		this->end.second = other.end.second;

		return *this;
	}

	GeometricLine& update(const std::pair<T, T>& newStart, const std::pair<T, T>& newEnd) {
		this->start = newStart;
		this->end = newEnd;
		
		return *this;
	}

	T getCCW(const std::pair<T, T>& target) const {
		return (end.first - start.first) * (target.second - start.second) - (end.second - start.second) * (target.first - start.first);
	}

	bool cross(const GeometricLine& target) {
		const T ourCCW = this->getCCW(target.start) * this->getCCW(target.end);
		const T theirCCW = target.getCCW(this->start) * target.getCCW(this->end);

		if (ourCCW == 0 && theirCCW == 0) {
			if (this->start > target.end || target.start > this->end) {
				return false;
			}
			else {
				return true;
			}
		}
		else {
			return (ourCCW <= 0 && theirCCW <= 0);
		}
	}

	const std::pair<T, T>& getStart(void) const {
		return start;
	}

	const std::pair<T, T>& getEnd(void) const {
		return end;
	}
	
	T lengthSquare(void) const {
	    T dx = end.first - start.first;
	    T dy = end.second - start.second;
	        
	    return dx * dx + dy * dy;
	}
};

#endif // !__GEOMETRIC_LINE_HPP__

main.cpp

#include <bits/stdc++.h>
#include "geometric_line.hpp"

using namespace std;

using ll = long long int;
using ull = unsigned long long int;

using pll = pair<ll, ll>;

class CCW_cmp {
private:
    pll origin;
   
public:
    CCW_cmp(const pll& origin) : origin(origin) {};
    bool operator()(const pll& A, const pll& B) {
        GeometricLine<ll> originToA(origin, A);
        ll ccw = originToA.getCCW(B);
       
        if (ccw < 0) {
            return false;
        }
        else if (ccw > 0) {
            return true;
        }
        else {
            GeometricLine<ll> originToB(origin, B);
            return originToA.lengthSquare() < originToB.lengthSquare();
        }
    }
};

int main(void) {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
   
    int N;
    cin >> N;
   
    vector<pll> points(N);
   
    for (int i=0; i<N; i++) {
        cin >> points[i].first >> points[i].second;
    }
   
    sort(points.begin(), points.end());
   
    CCW_cmp cmp(points[0]);
    sort(points.begin() + 1, points.end(), cmp);
   
    GeometricLine<ll> currentLine(points[0], points[1]);
    stack<pll> convexHull;
   
    convexHull.push(points[0]);
    convexHull.push(points[1]);
   
    for (int i=2; i<N; i++) {
        while (convexHull.size() >= 2) {
            ll ccw = currentLine.getCCW(points[i]);
           
            if (ccw > 0) {
                break;
            }
            else {
                convexHull.pop();
                if (convexHull.size() == 1) {
                    break;
                }
            }
           
            pll B = convexHull.top();
            convexHull.pop();
            pll A = convexHull.top();
            convexHull.push(B);
           
            currentLine = GeometricLine<ll>(A, B);
        }
       
        currentLine = GeometricLine<ll>(convexHull.top(), points[i]);
        convexHull.push(points[i]);
    }
    
    cout << convexHull.size();
   
    return 0;
}

그러자 모든 테스트 케이스를 통과하고 ‘맞았습니다’가 나오는 것을 확인할 수 있었다.

마무리

이 문제는 내가 군입대를 하고 나서 처음으로 푸는 CLASS 6 문제이다. 연등 시간 자체는 짧지만 알차게 활용하여 알고리즘을 공부할 수 있었던 좋은 문제였던 것 같다.

내가 만든 GeometricLine 자료구조는 내 레포지토리2에서 확인할 수 있다. 필요한 사람은 참고하길 바란다.

오늘의 PS는 여기까지!


1: https://www.acmicpc.net/problem/1708
2: https://github.com/ChoiCube84/baekjoon-solutions/blob/main/C%2B%2B/custom_data_structures/geometric_line.hpp