hvv_an 2025. 4. 22. 22:37

문제 설명

그래프는 정점과 간선으로 이루어져 있다. 두 정점 사이에 경로가 있다면, 두 정점은 연결되어 있다고 한다. 연결 요소는 모든 정점이 서로 연결되어 있는 정점의 부분집합이다. 그래프는 하나 또는 그 이상의 연결 요소로 이루어져 있다.

트리는 사이클이 없는 연결 요소이다. 트리에는 여러 성질이 있다. 예를 들어, 트리는 정점이 n개, 간선이 n-1개 있다. 또, 임의의 두 정점에 대해서 경로가 유일하다.

그래프가 주어졌을 때, 트리의 개수를 세는 프로그램을 작성하시오.

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


 

 

 

 

 

 

제한 사항


 

 

 

 

 

 

풀이

문제를 요약하면, 주어진 그래프에서 트리가 몇 개인지 구하면 된다.

 

해당 문제는 사이클 판정이 핵심이다.

일반적인 사이클 판정은 dfs를 통해 알아낼 수 있다.

연결된 노드를 순회하며 이미 방문한 노드에 다시 방문한다면 사이클이 존재하는 것이다.

bool Dfs(int start, int prev)
{
    visited[start] = true;
    for (auto node : graph[start])
    {
        if (node == prev) continue;

        if (visited[node]) return false;
        if (!Dfs(node, start)) return false;
    }

    return true;
}

 

이를 통해 사이클이 있는 노드들을 제외한 나머지 노드들의 개수를 세면 된다.

int cnt = 0;
for (int i = 1; i <= N; i++)
{
    if (visited[i]) continue;
    if (Dfs(i, 0)) cnt++;
}

 

처음에는 Union-Find를 통해 루트개수만 세면 된다고 생각했다.

근데 사이클 판정을 어떻게 해야 할지 모르겠어서 DFS로 전환하였는데 Union-Find로도 충분히 풀 수 있다고 하여 기록한다.

Union-Find에서 사이클 판정은 같은 부모를 갖는 노드를 연결한다면 사이클이 존재한다고 판정할 수 있다.

따라서, 두 노드의 부모가 같은 노드가 생긴다면 해당 노드의 parent를 유효하지 않은 값으로 변경하고 모든 간선을 처리한다면 트리가 이루어진 노드는 자기 자신을 parent로 갖는 노드가 반드시 1개 존재한다.

즉, 모든 간선을 처리한 후 모든 노드의 parent를 돌며 자기 자신인 개수를 세면 트리의 개수를 알 수 있다.

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

void Union(int a, int b)
{
    a = Find(a);
    b = Find(b);

    if (a == b)
    {
        parent[a] = 0;
        parent[b] = 0;
        return;
    }

    if (a > b) swap(a, b);
    parent[b] = a;
}
int cnt = 0;
for (int i = 0; i < M; i++)
{
    int a, b;
    cin >> a >> b;

    Union(a, b);
}

for (int i = 1; i <= N; i++)
{
    if (parent[i] == i) cnt++;
}

 

 

 

 

 

전체 코드

#include <bits/stdc++.h>
using namespace std;
#define INPUT_OPTIMIZE cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);
#define INF 2e9

using namespace std;
int N, M;

vector<vector<int>> graph;
vector<bool> visited;

bool Dfs(int start, int prev)
{
    visited[start] = true;
    for (auto node : graph[start])
    {
        if (node == prev) continue;

        if (visited[node]) return false;
        if (!Dfs(node, start)) return false;
    }

    return true;
}

int main() 
{
    INPUT_OPTIMIZE;

    int t = 1;
    while (true)
    {
        cin >> N >> M;
        if (N == 0 && M == 0) break;

        graph.resize(N + 1, vector<int>());
        visited.resize(N + 1);

        for (int i = 1; i <= N; i++)
        {
            graph[i].clear();
            visited[i] = false;
        }

        for (int i = 0; i < M; i++)
        {
            int a, b;
            cin >> a >> b;

            graph[a].push_back(b);
            graph[b].push_back(a);
        }

        int cnt = 0;
        for (int i = 1; i <= N; i++)
        {
            if (visited[i]) continue;
            if (Dfs(i, 0)) cnt++;
        }

        if (cnt > 1)
        {
            cout << "Case " << t++ << ": A forest of " << cnt << " trees.\n";

        }
        else if (cnt == 1)
        {
            cout << "Case " << t++ << ": There is one tree.\n";

        }
        else
        {
            cout << "Case " << t++ << ": No trees.\n";
        }
    }

    return 0;
}