본문으로 건너뛰기
김신건의 로그

볼록 다각형 내부 점 판정 (Point in Convex Polygon)

· 수정 · 📖 약 3분 · 1,023자/단어 #algorithm #geometry #point-in-convex-polygon
point in convex polygon, 볼록 다각형 내부 점, convex polygon point containment, binary search on angle

정의

볼록 다각형 내부 점 판정 은 볼록 다각형(convex polygon) 과 하나의 점이 주어졌을 때, 점이 다각형의 내부/경계/외부 중 어디에 있는지 O(log N) 에 판별하는 알고리즘. N 은 꼭짓점 개수. 다각형이 반드시 볼록 (내각 180도 미만) 이어야 하며, 꼭짓점이 반시계(CCW) 또는 시계(CW) 순서로 주어져야 한다.

문제 상황과 동기

Q 개의 점이 볼록 N 각형 내부에 있는지 묻는 문제.

  • naive: 모든 변에 대해 CCW 검사를 일일이 수행. O(N Q). N=10^5, Q=10^5 면 불가능.
  • binary search on angle: O(N) 전처리 + O(log N) 쿼리. 총 O(N + Q log N).

핵심 통찰: 볼록 다각형의 꼭짓점은 한 점(p_0) 을 기준으로 각도순으로 정렬되어 있다. 임의의 점 q 에 대해, q 가 속하는 “쐐기” (p_0, p_i, p_{i+1}) 를 이분 탐색으로 찾고, 그 삼각형 내부에 q 가 있는지 CCW 한 번으로 확인.

시각화

핵심 아이디어

invariant: 볼록 다각형의 꼭짓점 p_0 에서 다른 꼭짓점으로의 방향 벡터는 각도 순서 로 정렬되어 있다.

1. p_1 ... p_{N-1} 이 p_0 를 기준으로 반시계 방향 정렬
2. 쿼리 점 q 에 대해, p_0 에서 q 로의 벡터가 어느 구간
   (p_0, p_i) 와 (p_0, p_{i+1}) 사이에 있는지 이분 탐색
3. q 가 삼각형 (p_0, p_i, p_{i+1}) 내부에 있는지 CCW 로 확인
방향 판정결과
CCW(p_0, p_i, q) > 0q 가 왼쪽: i 를 오른쪽으로
CCW(p_0, p_i, q) ≤ 0q 가 오른쪽: i 를 왼쪽으로
CCW(p_i, p_{i+1}, q) ≥ 0q 가 내부 또는 경계
그 외외부

알고리즘

INPUT: convex polygon P[0..N-1] (CCW order), query point q
OUTPUT: IN / OUT / ON

// 1. q 가 P[0] 과 같은 점인지 확인
if P[0] == q: return ON

// 2. q 가 P[0]->P[1] 과 P[0]->P[N-1] 사이에 있는지 확인
if CCW(P[0], P[1], q) < 0 or CCW(P[0], P[N-1], q) > 0:
    return OUT

// 3. 이분 탐색: q 가 속하는 wedge 찾기
lo = 1, hi = N - 1
while hi - lo > 1:
    mid = (lo + hi) / 2
    if CCW(P[0], P[mid], q) >= 0: lo = mid
    else: hi = mid

// 4. lo 와 lo+1 로 이루어진 삼각형 내부 확인
if CCW(P[lo], P[lo+1], q) >= 0:
    if q 가 P[lo] 또는 P[lo+1] 경계 위: return ON
    return IN
return OUT

구현

// 볼록 다각형 내부 점 판정 O(log N)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Point { ll x, y; };
int ccw(Point a, Point b, Point c) {
  ll ret = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
  if (ret > 0) return 1;
  if (ret < 0) return -1;
  return 0;
}
string pointInConvexPolygon(vector<Point>& P, Point q) {
  int n = P.size();
  if (ccw(P[0], P[1], q) < 0 || ccw(P[0], P[n-1], q) > 0) return "OUT";
  if (ccw(P[0], P[1], q) == 0) {
      if (q.x >= min(P[0].x, P[1].x) && q.x <= max(P[0].x, P[1].x)
       && q.y >= min(P[0].y, P[1].y) && q.y <= max(P[0].y, P[1].y))
          return "ON";
      return "OUT";
  }
  int lo = 1, hi = n - 1;
  while (hi - lo > 1) {
      int mid = (lo + hi) / 2;
      if (ccw(P[0], P[mid], q) >= 0) lo = mid;
      else hi = mid;
  }
  int c = ccw(P[lo], P[lo+1], q);
  if (c > 0) return "IN";
  if (c < 0) return "OUT";
  return "ON";
}
int main() {
  int n, q; cin >> n >> q;
  vector<Point> P(n);
  for (auto& p : P) cin >> p.x >> p.y;
  while (q--) {
      Point qp; cin >> qp.x >> qp.y;
      cout << pointInConvexPolygon(P, qp) << "\n";
  }
}
stdin
5 3
0 0
6 0
5 4
2 5
-1 3
3 2
0 5
2 2
결과
IN
OUT
ON

복잡도

항목
전처리O(N)
쿼리 (점 1개)O(log N)
전체 (Q 쿼리)O(N + Q log N)
공간O(N)

변형 / 활용

  • 컨벡스 헐 점들을 CCW 정렬 후 hull 바깥 점을 빠르게 필터.
  • 다각형 내부 점 수: 쿼리당 O(log N), N=Q=10^5 에서 실용적.
  • Boundary-on: CCW=0 인 변 위에 있으면 projection 범위로 확인.
  • Rotating calipers 와 결합해 convex hull 회전 과정에서 포함 여부 확인.

함정

  1. CCW 방향: 다각형이 시계 방향이면 부호 반전 또는 reverse 후 처리.
  2. 오버플로우: 좌표 10^6 에서 CCW 는 10^12 → C++/Java long long/long 필수.
  3. 경계 판정: 점이 꼭짓점이나 변 위면 ON. 이분 탐색 전 p_0 일치 여부 먼저 확인.
  4. Collinear: 모든 점이 일직선이면 볼록 다각형이 아님. degenerate case 로 따로 처리.

BOJ 연습 문제

번호제목정답률링크
BOJ 2254감옥 건설33.149%kokoa-lab
BOJ 2049가장 먼 두 점37.784%kokoa-lab
BOJ 2699격자점 컨벡스헐57.516%kokoa-lab

참고

이 글의 용어 (4개)
각도 정렬 (Angle Sorting)algorithm
정의 각도 정렬 (Angle Sorting / Polar Sort) 은 2차원 평면 위의 점들을 기준점에 대한 극각 (polar angle) 순서로 정렬하는 기법. atan2 함…
기하 (Geometry) 기본algorithm
정의 Computational Geometry 는 점, 선, 다각형 등 기하 객체를 컴퓨터로 처리하는 알고리즘 분야. PS 에서는 2D 평면 위의 정수 좌표 가 주로 다루어지며,…
볼록 껍질 (Convex Hull)algorithm
정의 볼록 껍질 (Convex Hull) 은 주어진 점 집합 P 를 모두 포함하는 최소 크기의 볼록 다각형. 즉 P 의 어떤 점도 다각형 외부에 있지 않고, 다각형의 꼭짓점은 P…
Geometry Basic: 벡터, CCW, 외적, 내적algorithm
정의 기하 알고리즘의 밑바탕. 대부분의 2D/3D 기하 문제는 벡터 연산, 내적/외적, CCW 판정 세 가지의 조합으로 풀립니다. 벡터 2D 벡터: 두 점 $A = (ax, ay…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기