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

스위핑 (Sweeping)

· 수정 · 📖 약 3분 · 1,230자/단어 #algorithm #foundation #sweeping
sweeping, 스위핑, 라인 스위핑, line sweep, sweep line

정의

스위핑 (Sweeping) 은 시간 또는 공간 축 위의 이벤트를 정렬한 뒤, 한 방향으로 훑으며 상태를 갱신해 문제를 푸는 기법입니다. 선분 교차, 구간 합집합, 최대 겹침 수 등 기하 / 구간 문제에서 O(N log N) 정렬 + O(N) 스윕으로 naive O(N²) 를 피할 수 있습니다.

문제 상황과 동기

선분 N 개가 주어지고 “최대 몇 개가 겹치는가?”를 물을 때,

  • naive: 모든 점을 샘플링하거나 N² 페어 비교. 연속 좌표라면 불가능.
  • sweeping: 각 선분의 시작 / 끝을 이벤트로 만들어 정렬, 수평선(또는 시간선)을 왼쪽에서 오른쪽으로 밀면서 현재 활성 선분 수를 추적. 총 O(N log N).

핵심 통찰: 이벤트를 정렬하면, 상태 변화는 순서대로 한 번씩만 처리. 계산 기하 / 구간 스케줄링에서 가장 많이 쓰이는 패턴 중 하나입니다.

시각화

핵심 아이디어

invariant: 스윕 라인이 현재 위치 x 를 지날 때, 활성 상태인 객체들만 관리. 이벤트 발생 순서대로 처리하므로, 모든 객체를 동시에 들고 있을 필요가 없습니다.

events = []
for each segment [l, r]:
    events.append((l, +1))   # start
    events.append((r, -1))   # end
events.sort()

active = 0
max_active = 0
for x, delta in events:
    active += delta
    max_active = max(max_active, active)

확장:

  • 선분 교차 검사: Bentley-Ottmann 알고리즘. 수평선을 위에서 아래로 내리며 교차점 이벤트 추가.
  • 구간 합집합 길이: 정렬 후 병합. max(prev_end, curr_start) 비교.
  • 직사각형 면적 합: 수직 이벤트 + 세그먼트 트리.
  • Voronoi 다이어그램: Fortune’s algorithm 도 스위핑 기반.

알고리즘

최대 겹침 수 (Max Overlapping Intervals)

max_overlap(intervals):
    events = []
    for [l, r] in intervals:
        events.append((l, START))
        events.append((r, END))
    events.sort()

    active = 0, max_active = 0
    for (x, type) in events:
        if type == START:
            active++
        else:
            active--
        max_active = max(max_active, active)
    return max_active

구간 합집합 길이

union_length(intervals):
    events = sorted(intervals, key=lambda x: x[0])
    total = 0
    prev_end = -∞
    for (l, r) in events:
        if l > prev_end:
            total += (r - l)
            prev_end = r
        else:
            if r > prev_end:
                total += (r - prev_end)
                prev_end = r
    return total

구현

// N 개 구간 [l, r], 최대 몇 개가 동시에 활성?
#include <bits/stdc++.h>
using namespace std;
int main() {
  int n; cin >> n;
  vector<pair<int, int>> ev;
  for (int i = 0; i < n; i++) {
      int l, r; cin >> l >> r;
      ev.push_back({l, +1});
      ev.push_back({r, -1});
  }
  sort(ev.begin(), ev.end());
  int active = 0, mx = 0;
  for (auto [x, d] : ev) {
      active += d;
      mx = max(mx, active);
  }
  cout << mx << "\n";
}
stdin
4
1 4
2 5
3 6
7 8
결과
3

구간 합집합 예제

// N 개 구간 [l, r] 의 합집합 길이
#include <bits/stdc++.h>
using namespace std;
int main() {
  int n; cin >> n;
  vector<pair<int, int>> a(n);
  for (auto& [l, r] : a) cin >> l >> r;
  sort(a.begin(), a.end());
  int total = 0, prev = -1e9;
  for (auto [l, r] : a) {
      if (l > prev) {
          total += r - l;
          prev = r;
      } else if (r > prev) {
          total += r - prev;
          prev = r;
      }
  }
  cout << total << "\n";
}
stdin
3
1 4
3 7
10 12
결과
9

복잡도

항목
시간 (정렬)O(N log N)
시간 (스윕)O(N)
전체O(N log N)
공간O(N)

정렬이 병목. 이벤트 수가 2N (시작 + 끝) 이므로 상수가 약 2배.

변형 / 활용

문제기법복잡도
선분 교차 검사Bentley-Ottmann. 수평선을 내리며 active set (BST) 유지O(N log N + K log N), K = 교차점 수
직사각형 면적 합수직 이벤트 + [[Segtree세그트리]] / Coordinate Compression
Voronoi 다이어그램Fortune’s algorithm. 포물선 front 유지O(N log N)
Skyline Problem건물 시작 / 끝 이벤트 + 높이 multisetO(N log N)
구간 합집합정렬 + 병합O(N log N)

함정

1. 이벤트 순서 (시작 vs 끝)

같은 좌표에서 시작 이벤트가 끝 이벤트보다 먼저 와야 하는지 나중에 와야 하는지가 문제마다 다릅니다. 예: “구간 [l, r] 이 닫힌 구간이냐 열린 구간이냐?” 에 따라 active++ 와 active— 순서가 바뀝니다.

// 닫힌 구간: 같은 x 일 때 시작을 먼저
events.push_back({l, 0, +1});   // 0 = start
events.push_back({r, 1, -1});   // 1 = end
sort(events.begin(), events.end());

2. 좌표 압축 (Coordinate Compression)

좌표 범위가 10^9 이상이면 직접 배열 인덱스로 쓸 수 없습니다. 이벤트 좌표만 모아 압축한 뒤, 맵 / 정렬된 벡터로 접근합니다.

3. 실수 좌표

기하 문제에서 교차점이 실수일 때, 부동소수점 오차로 정렬 순서가 불안정할 수 있습니다. 정수 / 분수 연산 또는 epsilon 비교가 필요합니다.

4. 다차원 스윕

2D 는 수직 / 수평 두 축을 모두 스윕하거나, 한 축을 스윕하며 다른 축을 세그먼트 트리로 관리합니다. 구현이 복잡하므로 1D 로 환원 가능한지 먼저 확인합니다.

BOJ 연습 문제

번호제목정답률링크
BOJ 2170선 긋기-kokoa-lab
BOJ 1931회의실 배정-kokoa-lab
BOJ 2836수상 택시-kokoa-lab
BOJ 4792레드 블루 스패닝 트리-kokoa-lab

참고

이 글의 용어 (3개)
누적 합 (Prefix Sum)algorithm
정의 누적 합 (Prefix Sum) 은 배열 에 대해 (또는 1-indexed ) 을 미리 계산해 두고, 임의 구간 합 을 O(1) 에 구하는 정형. 문제 풀이에서 "구간 N …
두 포인터 (Two Pointer)algorithm
정의 두 포인터 (Two Pointer) 는 정렬 또는 단조 구조 에서 두 인덱스 , 을 서로 다른 속도 / 방향 으로 이동시키며 부분 구간 / 페어를 O(N) 에 탐색하는 기법…
세그먼트 트리 (Segment Tree)algorithm
정의 세그먼트 트리 (Segment Tree) 는 배열의 구간 쿼리 (range query) 와 점 갱신 (point update) 를 모두 O(log N) 에 처리하는 이진 트…

💬 댓글

사이트 검색 / 명령어

검색

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