ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [백준] 4196번 도미노
    문제 풀이 2020. 9. 11. 18:21

    4196 도미노


    문제


    도미노는 재밌다. 도미노 블록을 일렬로 길게 늘어세운 뒤 블록 하나를 넘어뜨리면 그 블록이 넘어지며 다음 블록을 넘어뜨리는 일이 반복되어 일렬로 늘어선 블록들을 연쇄적으로 모두 쓰러뜨릴 수 있다. 그러나, 가끔씩 도미노가 다른 블록을 넘어뜨리지 못하게 배치되어 있다면, 우리는 다음 블록을 수동으로 넘어뜨려야 한다.

    이제 각 도미노 블록의 배치가 주어졌을 때, 모든 블록을 넘어뜨리기 위해 손으로 넘어뜨려야 하는 블록 개수의 최솟값을 구하자.


    입력


    첫 번째 줄에 테스트 케이스의 개수가 주어진다.

    각 테스트 케이스의 첫 번째 줄에는 두 정수 N, M이 주어지며 두 수는 100,000을 넘지 않는다. N은 도미노의 개수를, M은 관계의 개수를 나타낸다. 

    도미노 블록의 번호는 1과 N 사이의 정수다. 다음 M개의 줄에는 각각 두 정수 x, 가 주어지는데, 이는 x번 블록이 넘어지면 y번 블록도 넘어짐을 뜻한다.


    출력


    각 테스트 케이스마다 한 줄에 정수 하나를 출력한다. 정답은 손으로 넘어뜨려야 하는 최소의 도미노 블록 개수이다.




    풀이)

    SCC와 약간의 위상정렬 방법(indegree[]배열)를 이용해서 푸는 문제이다.


    과정

    1. 블록끼리의 연결을 가지고, kosaraju algorithm을 이용해 SCC를 구한다.


       kosaraju algorithm : 2번의 dfs를 이용, stack을 활용


    2. SCC끼리의 연결 상태를 저장한다.

       ex) SCC 1 -> SCC 2 로 연결이 가능하다면 SCC 2의 indegree(들어오는 간선 수)를 +1 시켜준다.


    3. SCC 중 indegree 값이 0인 SCC의 개수를 세어서 출력한다. 



    (그림으로 예를 들자면)

    이런식으로 블록이 연결되어있다면



    아래처럼 SCC는 2개가 나오고, SCC 1과 SCC 2는 1->2로 연결될 수 있다.


    1->2 로 간선이 연결될 수 있으니깐 SCC 2의 간선 개수 +1


    그럼 총 SCC 개수는 2개이고 indegree가 0인 SCC는 1개니깐

    결론적으로 손으로 넘어트려야 하는 블록의 최소 개수는 1개이다.


    (indegree가 1이상인 SCC에 속한 블록들은 다른 SCC 블록들에 의해 넘어질 수 있다고 생각하면 된다.) 



    코드)


    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
    #include <stdio.h>
    #include <string.h>
    #include <vector>
    #include <stack>
    using namespace std;
    #define MAX 100002
    int n, m;
    vector <int> adj[MAX]; //kosaraju dfs1
    vector <int> trans_adj[MAX]; //kosaraju dfs2
    vector <int> connect_scc[MAX]; //SCC 끼리의 연결상태 저장
    int indegree[MAX]; //SCC마다 들어오는 간선의 수 저장
    bool visited[MAX];
    stack<int> st;
    int SCC[MAX]; //각 블록이 어떤 SCC에 속하는지 저장
     
    void dfs(int node) {
        visited[node] = true;
        for (int i = 0; i < adj[node].size(); i++) {
            int next = adj[node][i];
            if (visited[next]) continue;
            dfs(next);
        }
        st.push(node);
    }
    void dfs2(int node,int idx) {
        SCC[node] = idx; //해당 블록의 SCC번호를 저장
     
        for (int i = 0; i < trans_adj[node].size(); i++) {
            int next = trans_adj[node][i];
            if (SCC[next] == 0) {//연결된 블록이 아직 어떤 SCC에도 해당이 되지 않은 블록이라면 같은 SCC 번호로 속하게해줌
                dfs2(next, idx);
            }
            else if(SCC[next] != SCC[node]) { //연결된 블록이 다른 SCC에 속한 블록이라면
                indegree[SCC[node]]++
                connect_scc[SCC[next]].push_back(SCC[node]); //SCC끼리의 연결 상태 저장 
                //*주의점*  trans_adj는 블록의 연결상태를 반대로 저장한 것이므로
                //            SCC끼리의 연결상태를 저장할 때 현재SCC -> 다른 SCC로 연결상태를 저장하면 안되고
                //            원래 연결상태를 생각해서 다른 SCC -> 현재 SCC 로 연결상태를 저장해줘야한다.
            }
            
        }
    }
    int main() {
        int test_case;
        scanf("%d"&test_case);
        while (test_case--)
        {
            scanf("%d %d"&n, &m);
            int x, y;
            for (int i = 0; i < m; i++) {
                scanf("%d %d"&x, &y);
                adj[x].push_back(y);
                trans_adj[y].push_back(x);
            }
     
            //kosaraju dfs1
            for (int i = 1; i <= n; i++) {
                if (visited[i]) continue;
                dfs(i);
            }
            //kosaraju dfs2
            int idx = 1//SCC 번호
            while (st.size())
            {
                int here = st.top();
                st.pop();
                if (SCC[here] != 0) { //이미 SCC에 속한 블록이라면 pass
                    continue;
                }
                else {//속하지 않은 블록이라면
                    SCC[here] = idx; //해당 블록에 SCC 번호 저장해주고
                    dfs2(here, idx); //dfs 2수행
                    idx++//다음 SCC번호를 위해 +1
                }
                
            }
            
            //SCC끼리의
            int result = 0;
            for (int i = 1; i < idx; i++) {
                if (indegree[i] == 0) result++;
            }
            printf("%d\n", result);
     
            //다음 test_case를 위한 초기화
            for (int i = 1; i <= n; i++) {
                adj[i].clear();
                trans_adj[i].clear();
                indegree[i] = 0;
                visited[i] = false;
                connect_scc[i].clear();
                SCC[i] = 0;
            }
            
        }
        
     
        return 0;
    }
    cs


    '문제 풀이' 카테고리의 다른 글

    [백준] 3967번 매직스타  (0) 2020.09.16
    [백준] 9944번 NM보드 완주하기  (0) 2020.09.14
    [백준] 4013번 ATM  (0) 2020.09.05
    [백준] 13511번 트리와 쿼리 2  (0) 2020.09.02
    [백준] 17435번 합성함수와 쿼리  (0) 2020.09.01

    댓글

Designed by Tistory.