Coding Test/BaekJoon_C++

백준 2612 <선분 그룹> C++

JunOnJuly 2024. 4. 23. 00:34
728x90

https://www.acmicpc.net/problem/2162

 

2162번: 선분 그룹

첫째 줄에 N(1 ≤ N ≤ 3,000)이 주어진다. 둘째 줄부터 N+1번째 줄에는 양 끝점의 좌표가 x1, y1, x2, y2의 순서로 주어진다. 각 좌표의 절댓값은 5,000을 넘지 않으며, 입력되는 좌표 사이에는 빈칸이 하

www.acmicpc.net


선분이 교차하는지 판단하는 알고리즘과 그룹을 판단하기 위한 유니온 파인드 알고리즘을 함께 사용하면 풀 수 있습니다.

 

우선 선분이 교차하는지 판단하기 위해 두 개의 선분이 주어졌을 때 가질 수 있는 상태를 생각해보겠습니다.

위와 같이 열 가지의 상태가 존재하며 모든 상태를 고려해주겠습니다.

상태의 파악에는 CCW 알고리즘을 사용하겠습니다.

 

(0, 0) 의 경우에는 완전히 교차하는 상태로 각 선분-점 쌍의 CCW 를 계산해보면 (+, -), (+, -) 의 쌍으로 존재하며 두 선분 모두 CCW 쌍을 곱하면 음수가 나옴을 확인할 수 있습니다.

(0, 1) 의 경우에는 세 개의 점이 직선상에 위치하며 한 점이 걸치는 형태각 선분-점 쌍의 CCW 를 계산해보면 (0, -), (+, -) 혹은 (0, +), (+, -) 의 쌍으로 존재하며 하나의 CCW 쌍은 곱이 0, 나머지 CCW 쌍은 곱이 음수임을 알 수 있습니다.

...

같은 방식으로 모두 계산해보면

 

CCW 쌍의 곱 쌍: (-, -) -> 교차

CCW 쌍의 곱 쌍: (0, -) -> 교차

CCW 쌍의 곱 쌍: (0, 0) -> 교차

CCW 쌍의 곱 쌍: (0, +) -> 교차 X

CCW 쌍의 곱 쌍: (+, +) -> 교차 X

CCW 쌍의 곱 쌍: (+, +) -> 교차 X

CCW 쌍의 곱 쌍: (0, 0) -> 교차

CCW 쌍의 곱 쌍: (0, 0) -> 교차

CCW 쌍의 곱 쌍: (0, 0) -> 교차

CCW 쌍의 곱 쌍: (0, 0) -> 교차 X

 

(0, 0) 쌍을 제외하면 쌍과 결과가 1대1 매칭으로 단순한 if 문으로 처리할 수 있음을 알 수 있습니다.

(0, 0) 쌍에서 문제가 되는것은 (1, 4)두 선분이 같은 직선상에 있고 교차하지 않는 경우이 경우를 제외해주면 됩니다.

 

(선분 1의 모든 x 좌표가 선분 2 의 모든 x 좌표보다 크거나)

(선분 1의 모든 x 좌표가 선분 2 의 모든 x 좌표보다 작거나)

(선분 1의 모든 y 좌표가 선분 2 의 모든 y 좌표보다 크거나)

(선분 1의 모든 y 좌표가 선분 2 의 모든 y 좌표보다 작거나)

 

네 가지 조건중 하나를 만족하면 선분 1 과 선분 2 가 만나지 않으므로 위 경우를 제외해주면 됩니다.

 

선분이 교차하는지 판단할 조건을 알았으니 Union-Find 알고리즘을 통해 그룹을 병합 및 탐색하겠습니다.

 

임의의 시작 선분을 기준으로 방문하지 않은 다른 선분들을 순회하며 두 직선이 교차하는 경우 선분의 그룹을 병합해주고 교차하는 선분을 큐에 넣고 다시 과정을 반복하여 모든 선분을 방문하며 교차하는 선분을 업데이트 해줍니다.

 


#include <iostream>
#include <array>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>
#include <map>
#include <deque>

using namespace std;

// 점을 저장할 구조체
struct Point {
    // 좌표
    int x;
    int y;
};
// 직선 데이터를 저장할 구조체
struct Line {
    // 속한 점들
    Point point_1;
    Point point_2;
    // 속한 그룹
    int group;
    // 순회 여부
    bool visited = false;
};

// ccw
long long int ccw(Point point_1, Point point_2, Point point_3) {
    // 포인트 어레이
    array<Point, 4> points = { point_1, point_2, point_3, point_1 };
    // ccw 값
    long long int ccw_sum = 0;
    // 계산
    for (int i = 0; i < 3; i++) {
        ccw_sum += points[i].x * points[i + 1].y;
        ccw_sum -= points[i].y * points[i + 1].x;
    }

    return ccw_sum;
}

// 교차하는지 판단
bool is_cross(Line line_1, Line line_2) {
    // ccw
    long long int ccw_1 = ccw(line_1.point_1, line_1.point_2, line_2.point_1);
    long long int ccw_2 = ccw(line_1.point_1, line_1.point_2, line_2.point_2);
    long long int ccw_3 = ccw(line_2.point_1, line_2.point_2, line_1.point_1);
    long long int ccw_4 = ccw(line_2.point_1, line_2.point_2, line_1.point_2);
    // 곱한 값들
    long long int result_1 = ccw_1 * ccw_2;
    long long int result_2 = ccw_3 * ccw_4;
    // 값의 곱이 모두 음수면 완벽한 교차
    if (result_1 < 0 and result_2 < 0) return true;
    // 값의 곱이 모두 양수면 교차하지 않음
    else if (result_1 > 0 and result_2 > 0) return false;
    // 값의 곱의 하나가 0 이고 나머지가 음수이면 교차
    else if ((result_1 == 0 and result_2 < 0) or (result_1 < 0 and result_2 == 0)) return true;
    // 값의 곱의 하나가 0 이고 나머지가 양수이면 교차하지 않음
    else if ((result_1 == 0 and result_2 > 0) or (result_1 > 0 and result_2 == 0)) return false;
    // 값의 곱의 하나가 양수이고 나머지가 음수이면 교차하지 않음
    else if ((result_1 > 0 and result_2 < 0) or (result_1 < 0 and result_2 > 0)) return false;
    // 나머지 ( 값의 곱이 모두 0 )
    else {
        // 두 선분이 같은 직선상에 있고 만나지 않을 때
        if ((((line_1.point_1.x > line_2.point_1.x and line_1.point_2.x > line_2.point_1.x) and
            (line_1.point_1.x > line_2.point_2.x and line_1.point_2.x > line_2.point_2.x)) or
            ((line_1.point_1.y > line_2.point_1.y and line_1.point_2.y > line_2.point_1.y) and
                (line_1.point_1.y > line_2.point_2.y and line_1.point_2.y > line_2.point_2.y))) or

            (((line_2.point_1.x > line_1.point_1.x and line_2.point_2.x > line_1.point_1.x) and
                (line_2.point_1.x > line_1.point_2.x and line_2.point_2.x > line_1.point_2.x)) or
                ((line_2.point_1.y > line_1.point_1.y and line_2.point_2.y > line_1.point_1.y) and
                    (line_2.point_1.y > line_1.point_2.y and line_2.point_2.y > line_1.point_2.y)))) return false;
        // 나머지
        else return true;
    }
}

// 파인드
int Find(vector<Line>& lines, int& i) {
    // 자신의 그룹이 자신이면
    if (lines[i].group == i) return i;
    // 자신의 그룹이 자신이 아니면
    else {
        lines[i].group = Find(lines, lines[i].group);
    }
    return lines[i].group;
}

// 유니온
void Union(vector<Line>& lines, int& i, int& j) {
    // 두 선분의 그룹
    int group_i = Find(lines, i);
    int group_j = Find(lines, j);
    // 두 선분의 그룹이 다르면
    if (group_i != group_j) {
        // j 의 그룹을 i 의 그룹으로 변경
        lines[j].group = group_i;
        // 그룹을 변경했으면 false
    }
}

int main(void) {
    // 입력
    int N;
    cin >> N;
    // 직선을 저장할 벡터
    vector<Line> lines;
    for (int i = 0; i < N; i++) {
        Line line;
        cin >> line.point_1.x >> line.point_1.y >> line.point_2.x >> line.point_2.y;
        // 우선 자신은 자신의 그룹
        line.group = i;
        lines.push_back(line);
    }
    deque<int> dq = { 0 };
    lines[0].visited = true;
    // 직선 순회
    while (true) {
        // 큐가 비었으면
        if (dq.empty()) {
            // 직선 선택
            for (int i = 0; i < N; i++) {
                // 방문하지 않은 직선이면
                if (!lines[i].visited) {
                    // 큐에 삽입
                    dq.push_back(i);
                    // 방문 체크
                    lines[i].visited = true;
                    break;
                }
            }
            // 선택된 직선이 없으면 끝
            if (dq.empty()) break;
        }
        // 교차판단할 직선
        int line_1 = dq.front();
        dq.pop_front();
        // 선택된 직선과 다른 직선들 교차판단
        for (int i = 0; i < N; i++) {
            // 교차판단할 나머지 직선
            int line_2 = i;
            // 같은 직선이면 패스
            if (i == line_1) continue;
            // 이미 방문한 직선이면 패스
            if (lines[i].visited) continue;
            // 교차하면
            if (is_cross(lines[line_1], lines[line_2])) {
                // 방문 체크
                lines[line_2].visited = true;
                // 큐에 삽입
                dq.push_back(line_2);
                // 유니온
                Union(lines, line_1, line_2);
            }
        }
    }
    // 속한 그룹을 저장할 맵
    map<int, int> groups;
    // 직선을 순회
    for (int i = 0; i < N; i++) {
        // 그룹을 검색
        int group = Find(lines, i);
        // 이미 존재하는 그룹이면
        if (groups.find(group) != groups.end()) {
            // 그룹에 + 1
            groups[group] += 1;
        }
        // 존재하지 않으면
        else {
            // 그룹 추가
            groups.insert({ group, 1 });
        }
    }
    // 가장 크기가 큰 그룹에 속한 선분 수
    int max_lines = 0;
    for (auto pr : groups) {
        // 최신화
        max_lines = max(max_lines, pr.second);
    }

    cout << groups.size() << "\n" << max_lines;
}

 

728x90