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

Half-Plane Intersection

· 수정 · 📖 약 3분 · 969자/단어 #algorithm #geometry #half-plane-intersection
half-plane intersection, 반평면 교집합, halfplane intersection

정의

반평면 교집합 (Half-Plane Intersection) 은 N 개의 직선으로 정의된 반평면 ax + by + c <= 0 (또는 >= 0) 을 모두 만족하는 점들의 집합, 즉 볼록다각형 (convex polygon) 혹은 무한 영역 / 공집합을 구하는 기하 알고리즘.

문제 상황과 동기

각 반평면을 naive 하게 교집합하면 O(N^2). 즉, convex polygon 의 clipping 을 N 번 반복해야 한다. N=10^5 면 불가능.

핵심 통찰: 반평면을 각도(기울기) 순으로 정렬하고 deque 로 관리하면 convex hull 과 동일한 각도 정렬 + Amortized O(N) 테크닉 으로 O(N log N) 에 해결된다. Convex Hull 의 Graham scan 과 거의 같은 구조.

활용: 볼록다각형 내 최대 내접원, Minkowski 덧셈, 일부 LP (Linear Programming) 의 특수 케이스.

시각화

핵심 아이디어

각 반평면을 directed line p -> q 로 표현. valid side 는 항상 line 의 왼쪽 (CCW > 0). 각도 atan2(dy, dx) 순으로 정렬 후 deque 로 관리.

for each L in sorted order:
    while deque[-2] x deque[-1] NOT in L -> pop back
    while deque[0] x deque[1] NOT in L -> pop front
    push back L
마지막에 front/back 정리

각 반평면은 최대 한 번 push/pop. O(N).

알고리즘

halfplane_intersection(L):
    sort L by angle = atan2(dy, dx) (같은 angle: tight 한 것만 keep)
    deque = empty
    for each line in L:
        while |deque| >= 2 and cross(line, inter(deque[-2], deque[-1])) < 0:
            pop back
        while |deque| >= 2 and cross(line, inter(deque[0], deque[1])) < 0:
            pop front
        push back
    while |deque| >= 3 and cross(deque[0], inter(deque[-2], deque[-1])) < 0:
        pop back
    return [inter(deque[i], deque[(i+1)%|deque|]) for i]

구현

// Half-plane intersection, O(N log N)
#include <bits/stdc++.h>
using namespace std;
using ld = long double;
const ld EPS = 1e-9;
struct Pt { ld x, y; };
struct Line { Pt p, q; }; // valid: left side
ld cross(Pt a, Pt b) { return a.x*b.y - a.y*b.x; }
ld cross(Pt a, Pt b, Pt c) { return (b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x); }
Pt inter(Line a, Line b) {
  Pt va = {a.q.x-a.p.x, a.q.y-a.p.y};
  Pt vb = {b.q.x-b.p.x, b.q.y-b.p.y};
  Pt vc = {b.p.x-a.p.x, b.p.y-a.p.y};
  ld t = cross(vc, vb) / cross(va, vb);
  return {a.p.x + t*va.x, a.p.y + t*va.y};
}
ld angle(Line l) { return atan2(l.q.y-l.p.y, l.q.x-l.p.x); }
vector<Pt> halfplane(vector<Line> L) {
  sort(L.begin(), L.end(), [](Line a, Line b) { return angle(a) < angle(b); });
  deque<Line> dq;
  for (auto& l : L) {
      while (dq.size() >= 2 && cross(l.p, l.q, inter(dq[dq.size()-2], dq.back())) < EPS) dq.pop_back();
      while (dq.size() >= 2 && cross(l.p, l.q, inter(dq[0], dq[1])) < EPS) dq.pop_front();
      dq.push_back(l);
  }
  while (dq.size() >= 3 && cross(dq[0].p, dq[0].q, inter(dq[dq.size()-2], dq.back())) < EPS) dq.pop_back();
  vector<Pt> res;
  for (int i = 0; i < (int)dq.size(); i++) res.push_back(inter(dq[i], dq[(i+1)%dq.size()]));
  return res;
}
int main() {
  vector<Line> L = {{{0,0},{1,0}}, {{0,1},{0,0}}, {{6,0},{0,6}}, {{4,6},{-2,0}}};
  auto poly = halfplane(L);
  cout << fixed << setprecision(0);
  for (auto& p : poly) cout << "(" << p.x << ", " << p.y << ")\n";
}
결과
(0, 0)
(6, 0)
(2, 4)
(0, 2)

복잡도

항목
시간 (정렬)O(N log N)
시간 (Deque)O(N) (각 line 최대 1회 push/pop)
전체O(N log N)
공간O(N)

변형 / 활용

1. 볼록다각형 내 최대 내접원

Convex polygon 의 각 변을 안쪽으로 평행 이동 (offset) 하여 새로운 반평면들 생성. 그 교집합에 가장 큰 원이 들어갈 수 있는 중심점.

2. Minkowski Sum

두 볼록다각형 A, B 의 Minkowski sum A + B = {a + b | a in A, b in B}. 각 변의 방향별로 half-plane 으로 표현 가능.

3. LP (Linear Programming) 의 2D 특수 케이스

2변수 LP 는 half-plane intersection 으로 변환 가능. 후에 simplex 또는 Parametric Search 로 최적화.

함정

1. EPS 좌우 판정

cross(l.p, l.q, inter(...)) < EPS 로 반평면에 속하지 않음을 판정. EPS 를 너무 작게 잡으면 floating error 로 무한루프.

2. 무한 영역 / 공집합

결과가 유계 폴리곤이 아닐 수 있음. 빈 deque 나 2개 미만의 점이면 unbounded 또는 empty.

3. 평행 반평면

같은 각도의 반평면은 cross 값이 더 큰 (더 제약적인) 것만 유지.

BOJ 연습 문제

번호제목정답률링크
BOJ 16872Count the Half-Planes(수집 안 됨)kokoa-lab
BOJ 12795Half-Plane(수집 안 됨)kokoa-lab
BOJ 204201차원 Half-Plane(수집 안 됨)kokoa-lab

참고

이 글의 용어 (4개)
병렬 이분 탐색 (Parallel Binary Search)algorithm
정의 병렬 이분 탐색 (Parallel Binary Search, PBS) 은 Q 개의 쿼리를 각각 독립적으로 이분탐색하지 않고, 모든 쿼리의 이분탐색을 라운드별로 동시에 진행하…
볼록 껍질 (Convex Hull)algorithm
정의 볼록 껍질 (Convex Hull) 은 주어진 점 집합 P 를 모두 포함하는 최소 크기의 볼록 다각형. 즉 P 의 어떤 점도 다각형 외부에 있지 않고, 다각형의 꼭짓점은 P…
선분 교차 (Line Segment Intersection)algorithm
정의 선분 교차 판정 (Line Segment Intersection) 은 2D 평면 위 두 선분 AB 와 CD 가 서로 교차하는지 O(1) 에 판정하는 기하 연산. CCW (C…
스위핑 (Sweeping)algorithm
정의 스위핑 (Sweeping) 은 시간 또는 공간 축 위의 이벤트를 정렬한 뒤, 한 방향으로 훑으며 상태를 갱신해 문제를 푸는 기법입니다. 선분 교차, 구간 합집합, 최대 겹침…

💬 댓글

사이트 검색 / 명령어

검색

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