홈 BOJ 2162 - 선분 그룹
글
취소

BOJ 2162 - 선분 그룹

문제 링크

모델링: 교차 그래프와 연결 요소

각 입력 선분을 그래프의 정점으로 두고, 두 닫힌 선분이 교차할 때 두 정점을 간선으로 연결합니다. 그룹은 이 그래프의 연결 요소입니다. 직접 교차하지 않더라도 교차하는 선분들의 사슬로 이어져 있으면 같은 그룹입니다. 따라서 모든 선분 쌍을 검사하고 교차하는 쌍을 서로소 집합(DSU) 자료구조에서 합칩니다. 모든 검사가 끝난 뒤 DSU의 루트 개수가 그룹 수이고, 가장 큰 DSU 컴포넌트의 크기가 최대 그룹 크기입니다.

정확한 닫힌 선분 교차 판정

세 점 A, B, C에 대해 부호 있는 외적 (B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x) 의 부호로 C가 방향을 가진 직선 AB의 어느 쪽에 있는지 알 수 있습니다. 양수와 음수는 서로 반대쪽이고, 0은 세 점이 한 직선 위에 있음을 뜻합니다. 두 선분이 내부에서 제대로 교차하려면 각 선분의 양 끝점이 다른 선분을 지나는 직선에 대해 엄격히 반대 부호를 가져야 합니다. 외적 값을 서로 곱하지 말고 부호를 직접 비교해야 합니다. 외적 값 각각이 범위 안에 있더라도 곱은 오버플로할 수 있기 때문입니다.

공식 제한은 1 ≤ N ≤ 3,000, 각 좌표는 [-5,000, 5,000]입니다. 좌표 차이의 절댓값은 최대 10,000이므로 외적의 각 곱은 절댓값 최대 10^8, 두 곱의 차이는 절댓값 최대 2 × 10^8입니다. 따라서 부호 있는 64비트 long long에 안전하게 들어갑니다.

선분 쌍은 O(N^2)개입니다. 각 교차 판정은 상수 시간이고, 경로 압축과 크기 기준 합치기를 사용하는 DSU 연산은 분할상환 O(α(N)) 시간입니다. 총 시간 복잡도는 O(N^2 α(N))이며, 입력 선분과 DSU 배열의 보조 공간 복잡도는 O(N)입니다.

C++17 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

struct Point {
    long long x;
    long long y;
};

struct Segment {
    Point a;
    Point b;
};

long long cross(const Point& a, const Point& b, const Point& c) {
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

int orientation(const Point& a, const Point& b, const Point& c) {
    const long long value = cross(a, b, c);
    if (value > 0) return 1;
    if (value < 0) return -1;
    return 0;
}

bool onSegment(const Point& a, const Point& b, const Point& p) {
    return min(a.x, b.x) <= p.x && p.x <= max(a.x, b.x) &&
           min(a.y, b.y) <= p.y && p.y <= max(a.y, b.y);
}

bool oppositeSigns(int a, int b) {
    return (a < 0 && b > 0) || (a > 0 && b < 0);
}

bool intersects(const Segment& first, const Segment& second) {
    const Point& a = first.a;
    const Point& b = first.b;
    const Point& c = second.a;
    const Point& d = second.b;

    const int abC = orientation(a, b, c);
    const int abD = orientation(a, b, d);
    const int cdA = orientation(c, d, a);
    const int cdB = orientation(c, d, b);

    if (oppositeSigns(abC, abD) && oppositeSigns(cdA, cdB)) return true;

    if (abC == 0 && onSegment(a, b, c)) return true;
    if (abD == 0 && onSegment(a, b, d)) return true;
    if (cdA == 0 && onSegment(c, d, a)) return true;
    if (cdB == 0 && onSegment(c, d, b)) return true;
    return false;
}

class DSU {
public:
    explicit DSU(int n) : parent(n), size(n, 1) {
        for (int i = 0; i < n; ++i) parent[i] = i;
    }

    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }

    void unite(int a, int b) {
        a = find(a);
        b = find(b);
        if (a == b) return;
        if (size[a] < size[b]) swap(a, b);
        parent[b] = a;
        size[a] += size[b];
    }

    int componentSize(int root) const {
        return size[root];
    }

private:
    vector<int> parent;
    vector<int> size;
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<Segment> segments(n);
    for (Segment& segment : segments) {
        cin >> segment.a.x >> segment.a.y >> segment.b.x >> segment.b.y;
    }

    DSU dsu(n);
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            if (intersects(segments[i], segments[j])) dsu.unite(i, j);
        }
    }

    int groupCount = 0;
    int largestGroup = 0;
    for (int i = 0; i < n; ++i) {
        if (dsu.find(i) == i) {
            ++groupCount;
            largestGroup = max(largestGroup, dsu.componentSize(i));
        }
    }

    cout << groupCount << '\n' << largestGroup << '\n';
    return 0;
}
이 글은 저자가 CC BY 4.0 라이선스로 배포합니다.