백준 1707 - 이분 그래프

이번 문제는 백준 골드 4 이분 그래프 문제이다.
제목을 보고 그래프 문제이길래 흥미로워 보여 풀기로 마음 먹었다.
문제를 한 번 차근차근 읽어보도록 하자.

최대 2만개의 정점과, 20만개의 간선이 주어질 수 있다.
위 제약 조건 아래에서 그래프가 주어졌을 때, 이분 가능한지 판단하라.
일단 이 문제를 풀려면 이분 그래프가 무엇인지 알아야 한다.

이분 그래프(Bipartite graph)

이분 그래프는 이름 그대로, 두 집합으로 분할이 가능한 집합이다.
조금 더 자세하게 정의하면 각 정점들을 두 개의 집합으로 나누었을 때,
같은 집합에 포함된 정점들 간에는 간선이 없어야 한다.
아래와 같은 그래프가 이분 그래프이며, 색깔로 집합을 구분한다.
스크린샷 2023-12-01 오후 4 47 45

자 이제 이분 그래프가 무엇인지 알았으니, 어떻게 판별할지 고민해보자.
가장 큰 특징인 같은 집합의 노드끼리는 연결되지 않았음을 어떻게 확인할까?
집합에 정점을 집어넣는 모든 경우의 수를 다 탐색해보아야 할까?
완전탐색은 정점이 2만개이기 떄문에 시간이 말도 안되게 걸릴 것이다.

DFS + 색깔

위의 그래프에서 색깔을 이용해서 각 집합을 구분했었다.
DFS를 돌려서 색깔로 두 집합으로 구분이 가능한지 살펴 볼 수 있을까?

색깔로 취급할 int[] 배열을 하나 만들어서 색을 체크하도록 하고,
DFS를 우선 한 번 돌려서 두 집합으로 구분을 한 다음 더 작은 집합을 찾는다.
정점의 수가 더 작은 집합의 정점들로 부터 각각 DFS를 돌렸을 때,
색깔 배열의 상태가 변하지 않는다면 이분 그래프일 것이다.
이 논리를 코드로 구현했을 때, 시간초과가 발생하지 않을까?

곰곰히 생각해보니, 시간 초과가 문제가 아니라는 생각이 들었다.
애초에 이 논리는 끊어진 정점이 있다는 가정하에 이루어지며,
모두 이어진 그래프의 경우에는 판별이 불가능하다.

진짜 DFS + 색깔

사실 아이디어는 맞았는데, 논리가 잘못되었었다.
훨씬 간단하게 이분 그래프를 분별할 수 있는 방법이 인터넷에 있었다.
그것은 바로 DFS를 탐색하며 이어진 정점은 계속 반대 색깔로 갱신하는 것이다.
아래와 같이 코드로 구현하였다.

void dfs(int curr_node) {
    if (!visit[curr_node])
        visit[curr_node] = 1;
    // 특정 색으로 변경

    for (int i = 0; i < graph[curr_node].size(); i++) {
        if (!visit[graph[curr_node][i]]) {
            if (visit[curr_node] == 1)
                visit[graph[curr_node][i]] = 2;
            else if (visit[curr_node] == 2)
                visit[graph[curr_node][i]] = 1;

            dfs(graph[curr_node][i]);
        }
    }
}

정점을 계속 반대 색으로 갱신한 뒤, 핵심은 이를 검사할 때에 있다.
만약 모든 연결된 정점을 검사할 때 연결된 정점에서 같은 색이 발견된다면?
그럼 이 그래프는 이분 그래프가 될 수 없다는 의미가 된다!
왜냐하면, 어떤 두 정점이 있다면 그 둘은 반드시 다른 색을 가져야 하기 때문이다.
그게 바로 이분 그래프의 정의 이기 때문이다.

bool check_bipartited() {
    for (int i = 1; i <= vertex; i++) {
        for (int j = 0; j < graph[i].size(); j++)
            if (visit[i] == visit[graph[i][j]])
                return false;
    }

    return true;
}

for (int j = 1; j <= vertex; j++)
        if (!visit[j])
            dfs(j);

if (check_bipartited())
      cout << "YES" << '\n';
else
      cout << "NO" << '\n';

clear_array();

위와 같은 코드로 구현할 수 있다.
여러개의 테스트 케이스가 존재하기 때문에, 마지막엔 배열을 초기화 해준다.

혼자 생각해내기에는 아직 무리가 있는 문제였다.
이분 그래프를 판별 해낼 수 있는 아이디어를 생각하기가 매우 어려웠다.
이럴 때 일수록 더욱 개념을 잘 생각해야 하는 게 수학 문제를 푸는 것 같다.