Problem Solving/BOJ

[백준 10265번] [DFS / SCC] MT

  • -
728x90
반응형

위 문제에서 나타나게 되는 그래프를 잘 관찰해보면 사이클이 반드시 등장하게 되는 것을 관찰할 수 있다.  즉 1개의 Component를 기준으로 사이클이 존재하고 해당 사이클에 속한 노드를 종점으로 하는 간선을 outdegree로 가지는 노드들이 존재하는 양상을 띄고 있다. 

 

1개의 Component를 기준으로 사이클에 존재하는 것들은 서로서로 순환적으로 요구하고 있으므로 모두 다 버스에 타거나 전부 타지 말아야 한다. 따라서 해당 특성을 잘 고려하면 해당 component에서 버스를 탈 수 있는 인원은 사이클에 속한 사람 ~ 해당 component에 속한 사람이 된다.

 

그리고 최대 인원이 k명이라는 점을 잘 생각해보면 0/1 knapsack 문제와 비슷한 점을 발견할 수 있다. 왜냐하면 해당 component를 추가하기 위해서는 적어도 사이클에 속한 사람이 들어가야하는데 이를 더해서 k를 넘게 되면 해당 component를 추가할 수 없고, k를 넘지 않는다면 무조건 넣을 수 있기 때문이다. 

 

dp[i][j] : 무조건 j명 이상이 타야하는 상황일 때, 버스에 탈 수 있는 사람의 최대값 (단, i번째 component까지 고려)

(Knapsack DP에서 cost를 이 문제에서는 무조건 타야하는 사람의 인원수로 바꿔주면 된다는 아이디어에서 착안하였다.)

 

# Case 1 : dp[i][j] = dp[i - 1][j] (j < comp[i].first) (단, comp[i] = i번째 component의 버스를 탈 수 있는 인원의 최소값과 최대값을 pair값으로 가지고 있는 값)

# Case 2 : dp[i][j] = min(k, max(dp[i - 1][j - comp[i].first] + comp[i].second, dp[i - 1][j])) ( comp[i].first $\le$ j $\le$ k)

 

다만, 이렇게 DP로 풀기 위해서는 각 component 별로 사이클에 속한 인원 수와 component에 속한 인원수를 모두 구할 수 있어야 한다.

총 2가지 접근 방법으로 해결하였는데, 각각의 접근방법은 코드를 보면서 설명하도록 하겠다.

#include <bits/stdc++.h> #define fastio ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0) using namespace std; typedef pair<int, int> pii; int n, k; int visited[1001]; int finished[1001]; vector<vector<int> > r(1001); int r_o[1001]; int cycle_count; vector<pii> cycle_store; bool flag; int dp[1001][1001]; int dfs(int x){ visited[x] = 1; int result = 0; for(int i = 0; i < r[x].size(); i++){ int to = r[x][i]; if(!visited[to]) result += dfs(to); else{ if(!finished[to]){ flag = true; for(int i = x; i != to; i = r_o[i]){ cycle_count++; } cycle_count++; } } } finished[x] = 1; return result + 1; } int main(void){ fastio; memset(visited, 0, sizeof(visited)); memset(finished, 0, sizeof(finished)); memset(dp, 0, sizeof(dp)); cin >> n >> k; for(int i = 1; i <= n; i++){ int t; cin >> t; r_o[i] = t; r[t].push_back(i); // Edge reversal (Using scc property) } if(n == k){ cout << n << "\n"; return 0; } for(int i = 1; i <= n; i++){ if(!visited[i]){ cycle_count = 0; flag = false; int component_num = dfs(i); int cycle_num = cycle_count; if(!flag){ memset(visited, 0, sizeof(visited)); memset(finished, 0, sizeof(finished)); } else{ if(cycle_num <= k){ cycle_store.push_back(make_pair(cycle_num, component_num)); } } } } // 가능한 케이스가 없는 경우 if(cycle_store.empty()){ cout << 0 << "\n"; return 0; } for(int i = cycle_store[0].first; i <= k; i++){ dp[0][i] = min(k, cycle_store[0].second); } for(int i = 1; i < cycle_store.size(); i++){ for(int j = 0; j < cycle_store[i].first; j++){ dp[i][j] = dp[i - 1][j]; } for(int j = cycle_store[i].first; j <= k; j++){ dp[i][j] = min(k, max(dp[i - 1][j], dp[i - 1][j - cycle_store[i].first] + cycle_store[i].second)); } } int result = -1; for(int i = 0; i <= k; i++){ result = max(result, dp[cycle_store.size() - 1][i]); } cout << result << "\n"; return 0; }

맨 처음 방법은 주어진 간선들의 시점과 종점을 바꾸게 되면, 기존의 모든 간선에서 1개씩 outdegree가 있었으므로 모든 노드들의 indegree가 적어도 1개 이상 발생하게 된다. 따라서 cycle을 기준으로 모든 간선을 dfs를 통해 방문할 수 있게 된다.

 

사이클을 발견하게 되면 visited와 finished 배열을 초기화한 후 사이클에 속한 원소 중 1개를 dfs의 루트 노드로 제공하여 component 안에 속한 노드들의 개수를 세어주었다. 이 과정에서 단순히 초기화히여도 되는지에 대한 의구심을 제기할 수 있으나, 1개의 component를 기준으로 방문할 수 있는 모든 정점들을 방문하므로 다른 component를 연산할 때 영향을 끼치지 않으므로 일반성을 잃지 않는다고 할 수 있겠다. (참고 : https://everenew.tistory.com/145)

 

[백준] No.10265 - MT (C++, SCC, Knapscak)

문제 https://www.acmicpc.net/problem/10265 10265번: MT 남규는 동기들과 엠티를 가기 위해 버스를 대절했다. 그런데 과사의 실수로 대절버스의 인원이 잘못되어 남규의 동기들을 모두 태울 수 없었다.

everenew.tistory.com

#include <bits/stdc++.h> #define fastio ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0) using namespace std; typedef pair<int, int> pii; int n, k; int dp[1001][1001]; int visited[1001]; int finished[1001]; bool in_cycle[1001]; int r[1001]; int comp_cnt; vector<pii> comp_store; int dfs(int x){ visited[x] = 1; int to = r[x]; if(!visited[to]) dfs(to); else{ if(finished[to] == -1){ for(int i = to; i != x; i = r[i]){ in_cycle[i] = true; } in_cycle[x] = true; return finished[x] = comp_cnt++; } } return finished[x] = finished[to]; } int main(void){ fastio; memset(visited, 0, sizeof(visited)); memset(finished, -1, sizeof(finished)); memset(in_cycle, 0, sizeof(in_cycle)); memset(dp, 0, sizeof(dp)); cin >> n >> k; for(int i = 1; i <= n; i++){ int t; cin >> t; r[i] = t; } for(int i = 1; i <= n; i++){ if(!visited[i]){ dfs(i); } } comp_store = vector<pii> (comp_cnt, make_pair(0, 0)); for(int i = 1; i <= n; i++){ if(in_cycle[i]){ comp_store[finished[i]].first++; comp_store[finished[i]].second++; } else comp_store[finished[i]].second++; } if(comp_store[0].first <= k){ dp[0][comp_store[0].first] = min(k, comp_store[0].second); } for(int i = 1; i < comp_store.size(); i++){ for(int j = 0; j < comp_store[i].first; j++){ dp[i][j] = dp[i - 1][j]; } for(int j = comp_store[i].first; j <= k; j++){ dp[i][j] = min(k, max(dp[i - 1][j], dp[i - 1][j - comp_store[i].first] + comp_store[i].second)); } } int result = -1; for(int i = 0; i <= k; i++){ result = max(result, dp[comp_store.size() - 1][i]); } cout << result << "\n"; return 0; }

다음은, 어떤 노드에서 시작하든 무조건 사이클을 마주할 수 밖에 없다는 특성을 활용해준 것이다.

finished 배열을 component index에 해당하는 내용을 저장하게끔 하는 컨테이너로 사용해주면 된다. 따라서 방문할 수 있든, 방문할 수 있지 않든 finished배열을 가려는 방향의 정보를 그대로 가져와 저장해주면 된다.

 

이 부분때문에 상당히 어려웠다~

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.