Half-Plane 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 16872 | Count the Half-Planes | (수집 안 됨) | kokoa-lab |
| BOJ 12795 | Half-Plane | (수집 안 됨) | kokoa-lab |
| BOJ 20420 | 1차원 Half-Plane | (수집 안 됨) | kokoa-lab |
참고
- Convex Hull (비슷한 Deque 구조)
- 스위핑
- 선분 교차 판정
이 글의 용어 (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) 은 시간 또는 공간 축 위의 이벤트를 정렬한 뒤, 한 방향으로 훑으며 상태를 갱신해 문제를 푸는 기법입니다. 선분 교차, 구간 합집합, 최대 겹침…
💬 댓글