문제 풀이

[백준] 4013번 ATM

컴영 2020. 9. 5. 19:32

4013 ATM


문제)

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


풀이)

SCC, dp를 이용해서 푸는 어려웠던 문제였다.


같은 간선을 여러번 사용할 수 있기 때문에,

임의의 정점을 방문한다고 할때 그 정점이 속한 SCC내의 모든 정점을 방문해서 최대 현금을 구한다.


과정

1. kosaraju algorithm을 이용해 SCC을 구한다.

   SCC를 구할때, 각 정점이 어떤 SCC에 속하는지 표시해준다 //int node[i] => i 번 정점이 속하는 SCC번호

                      각 정점이 속하는 SCC의 총 현금을 저장해준다 //int scc[i] => i 번 SCC의 총 현금, 즉 SCC에 속한 정점들의 현금 합

     SCC 끼리 연결될 수 있다면, 그 연결 간선을 저장해준다 //vector<int> adj_scc[i] => i번 SCC와 연결되있는 다른 SCC 번호 저장


2. 레스토랑이 속한 SCC를 체크한다. // bool rest[i] == true => i번 SCC 내에는 레스토랑이 존재


3. SCC끼리의 연결 정보를 1번 과정에서 미리 저장했으므로, 그 정보를 이용해

   출발 정점 SCC에서 각 레스토랑 정점이 속한 SCC까지 BFS로 탐색한다.


   (탐색하면서, 레스토랑이 속한 SCC에 도달할 때 얻을 수 현금값을 배열에 memoization해준다.)


   //int dp[i] = 출발지 SCC에서 레스토랑이 속한 i번 SCC까지 도달하는 데 얻을 수 있는 최대 현금값


4. dp[i] 중 최대값을 출력한다.



요약 ∴ SCC를 이용한 DAG를 만들고, 그 그래프을 BFS로 탐색하며 DP로 현금 합 최대를 찾는다.



설명하기 어렵다.. 중간중간 사용하는 변수명들이 많아 햇갈릴 수 있으니

코드를 한줄씩 읽어보는 게 도움이 될 것이다.



코드)

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
#include <stdio.h>
#include <string.h>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
 
int N, M, x, y, s, p;
int cash[500001]; //각 정점의 돈
bool visited[500001];
bool rest[500001]; //scc안에 레스토랑 정점이 속해있는지 확인
vector<int> adj[500001];
vector<int> adj2[500001];
vector <int> adj_scc[500001]; //각 scc끼리 연결간선 저장
stack<int> st;
int scc[500001];//각 scc에 속하는 정점들의 현금 합
int node[500001];//각 정점이 어떤 scc에 속하는지
int scc_num = 1;
int dp[500001];
 
void dfs(int v) {
    visited[v] = true;
    for (int i = 0; i < adj[v].size(); i++) {
        if (visited[adj[v][i]])continue;
        dfs(adj[v][i]);
    }
    st.push(v);
}
void dfs2(int v, int num) {
    visited[v] = true;
    scc[num] += cash[v];
    node[v] = num;
    for (int i = 0; i < adj2[v].size(); i++) {
        int next = adj2[v][i];
        if (visited[next]) { //이미 방문한 정점
            if (node[next] != node[v]) {//방문했던 정점이 다른 scc에 속하는 정점이면
                adj_scc[node[next]].push_back(node[v]);//scc끼리 연결
            }
    
        }
        else {
            dfs2(next, num);
        }
    }
}
int main() {
    scanf("%d %d"&N, &M);
    for (int i = 0; i < M; i++) {
        scanf("%d %d"&x, &y);
        adj[x].push_back(y);
        adj2[y].push_back(x);
    }
    for (int i = 1; i <= N; i++) {
        scanf("%d"&cash[i]);
    }
    scanf("%d %d"&s, &p);
    
    //과정 1
    //kosaraju algorithm : scc 만듬
    for (int i = 1; i <= N; i++) {
        if (visited[i]) continue;
        dfs(i);
    }
    memset(visited, falsesizeof(visited));
    while (!st.empty())
    {
        int here = st.top();
        st.pop();
        if (visited[here])continue;
        dfs2(here, scc_num++);
 
    }
    //과정 2
    //레스토랑 정점이 속한 scc 표시
    for (int i = 1; i <= p; i++) {
        int temp;//레스토랑 정점번호
        scanf("%d"&temp);
        rest[node[temp]] = true;
    }
    //과정 3
    //출발지 scc부터 scc들 사이 이동하며, 도달 가능한 레스토랑scc까지 최고 현금값 구함, dp + bfs
    queue<int> qu;
    qu.push(node[s]);//출발지 scc부터
    dp[node[s]] = scc[node[s]];
    int result = 0;
    while (!qu.empty())
    {
        int here = qu.front();//scc번호
        qu.pop();
        if (rest[here]) {//레스토랑이 속한 scc 였다면
            result = result < dp[here] ? dp[here] : result;
        }
        for (int i = 0; i < adj_scc[here].size(); i++) {
            int next = adj_scc[here][i];//연결된 다른 scc
            if (dp[next] < dp[here] + scc[next]) {
                dp[next] = dp[here] + scc[next];
                qu.push(next);
            }
        }
    }
    printf("%d\n", result);
    return 0;
}
cs