
[문과 코린이의 IT 기록장] C++ 백준 문제풀이[그래프 1] - 서울 지하철 2호선 (16947)
16947번: 서울 지하철 2호선
첫째 줄에 역의 개수 N(3 ≤ N ≤ 3,000)이 주어진다. 둘째 줄부터 N개의 줄에는 역과 역을 연결하는 구간의 정보가 주어진다. 같은 구간이 여러 번 주어지는 경우는 없고, 역은 1번부터 N번까지 번호
www.acmicpc.net
[ 문제 ]
서울 지하철 2호선은 다음과 같이 생겼다.

지하철 2호선에는 51개의 역이 있고, 역과 역 사이를 연결하는 구간이 51개 있다. 즉, 정점이 51개이고, 양방향 간선이 51개인 그래프로 나타낼 수 있다. 2호선은 순환선 1개와 2개의 지선으로 이루어져 있다. 한 역에서 출발해서 계속 가면 다시 출발한 역으로 돌아올 수 있는 노선을 순환선이라고 한다. 지선은 순환선에 속하는 한 역에서 시작하는 트리 형태의 노선이다.
두 역(정점) 사이의 거리는 지나야 하는 구간(간선)의 개수이다. 역 A와 순환선 사이의 거리는 A와 순환선에 속하는 역 사이의 거리 중 최솟값이다.
지하철 2호선과 같은 형태의 노선도가 주어졌을 때, 각 역과 순환선 사이의 거리를 구해보자.
[ 입력 ]
첫째 줄에 역의 개수 N(3 ≤ N ≤ 3,000)이 주어진다. 둘째 줄부터 N개의 줄에는 역과 역을 연결하는 구간의 정보가 주어진다. 같은 구간이 여러 번 주어지는 경우는 없고, 역은 1번부터 N번까지 번호가 매겨져 있다. 임의의 두 역 사이에 경로가 항상 존재하는 노선만 입력으로 주어진다.
[ 출력 ]
총 N개의 정수를 출력한다. 1번 역과 순환선 사이의 거리, 2번 역과 순환선 사이의 거리, ..., N번 역과 순환선 사이의 거리를 공백으로 구분해 출력한다.
[ 코드 1 ]
#include<iostream> #include<algorithm> #include<cstring> // memset 사용 위해 #include<queue> using namespace std; #define MAX 3001 // 값이 1부터 시작하므로, 즉 0을 사용하지 않으므로, 3001로 설정 (define은 ; 사용x) /* 인접리스트 구현 vector <int> a[n] : 사용자가 앞으로 입력하게 되는 갯수만큼 2차원 배열이 생성된다. (입력은 push_back 활용) */ vector <int> a[MAX]; // 간선 bool check[MAX]; // 방문여부 bool hasCycle; // Cycle을 가진다면 true로 변환 (디폴트값 false) int pre[MAX]; // 이전에 방문했던 노드들 bool cycle[MAX]; // Cycle 노드 저장 int dist[MAX]; // 거리 저장 int N; // 역의 개수 (정점의 개수) // bfs : 너비우선탐색 void bfs() { queue<pair<int, int>> q; // pair<type, type> : 2개의 각각 지정한 타입을 저장하며, 저장한 값은 .first, .second로 각각 접근 가능) // 2개의 연관된 값에서, 각각의 조건에 따라 정렬한 결과를 얻고자 할 때 사용하면 좋다. for (int i = 1; i <= N; i++) { /* 사이클에 존재하는 노드라면? */ if (cycle[i]) { check[i] = true; // 사이클에 존재하는 노드들은, 이미 처리했다고 바꾸기 q.push({ i,0 }); // 사이클에 존재하는 노드들은, 거리상 0의 값을 가짐 // cf. dist[]의 경우에는, 0으로 디폴트값이 설정되어 있으므로, 따로 입력해줄 필요 x } } while (!q.empty()) { // 사이클 내에 있는 한 지점으로부터, 연결된 간선에 대해 하나씩 거리 측정 시작 int cur = q.front().first; // 해당 정점 int dis = q.front().second; // 거리 q.pop(); check[cur] = true; for (int i = 0; i < a[cur].size(); i++) { int next = a[cur][i]; // next = 현재 정점과 연결된 다음 정점 if (check[next]) // 아직 거쳐오지 않았다면 { continue; } dist[next] = dis + 1; // 다음 정점에, 거리 + 1 저장 q.push({ next,dis + 1 }); // 큐에 넣기 } } } // 사이클 찾기 함수 void findCycle(int cur) { check[cur] = true; for (int i = 0; i < a[cur].size(); i++) { /* 사이클을 찾았다면, 종료*/ if (hasCycle) return; // 다음 방문 노드 int next = a[cur][i]; /* 방문한 적이 있으면 실행*/ if (check[next]) { // 부모가 아닌, 다른 방문했던 노드이면 사이클임. if (next != pre[cur]) { cycle[cur] = true; // 사이클에 해당하는 노드들은, 모두 순환선에 포함됨. (따라서 순환선과의 거리는 0이 됨) hasCycle = true; // 사이클을 찾았다고 바꿔주기 // cur의 부모노드가 결국, 사이클을 이루는 첫 노드가 될 때까지 while문 돌리기 while (cur != next) { cycle[pre[cur]] = true; // cur의 부모 노드가, 사이클(순환선)에 포함되어있다고 바꿔주기 cur = pre[cur]; // cur의 부모노드로, cur를 바꿔주기 } return; } } /* 방문한 적이 없으면 실행 */ else { pre[next] = cur; // next 이전에 방문했던 노드는, cur이다. findCycle(next); } } } int main() { cin >> N; /* 역이 개수(정점의 개수) = 역과 역을 연결하는 구간의 정보 (간선의 개수) */ for (int i = 0; i < N; i++) // 역과 역을 연결하는 구간의 정보 (간선의 정보) { int from, to; cin >> from >> to; a[from].push_back(to); a[to].push_back(from); } // Cycle이 존재하는지 찾기. (존재한다면, 그 해당 cycle[]을 true로 변환해주기) findCycle(1); // bfs에서 활용하기 위해, findCycle에서 사용했던 check를 fasle로 리셋해주기 memset(check, false, 3001); // bfs 실행 bfs(); // 해당 정점과 순환선 사이의 거리 출력 for (int i = 1; i <= N; i++) { cout << dist[i] << ' '; } return 0; }
[ 코드 2 ]
#include<iostream> #include<algorithm> #include<queue> #include<cstring> #include <vector> using namespace std; #define MAX 3001 int N; bool Cycle; // cycle을 가진다면 true로 변환 bool Visit[MAX]; // 방문여부 bool Check_Cycle_Station[MAX]; // 순환선은 하나가 아니라 여러개가 될 수도 있음. 따라서 사이클의 시작점인지를 기록하는 곳이 필요 vector <int> Station[MAX]; // 간선 정보 저장 vector <int> Answer; // 거리 정보 저장 void Input() { cin >> N; // 간선의 정보 저장 (역과 역을 연결하는 구간의 정보) for (int i = 0; i < N; i++) { int a, b; cin >> a >> b; Station[a].push_back(b); Station[b].push_back(a); } } /* 순환선인지를 파악하기 위해 DFS함수를 사용 */ // DFS (현재 역, 시작 역, 경로의 수) // 시작역을 고정시켜놓고 하기 때문에, 실제 함수를 호출할 때는 1-N번까지 모두 한번씩 시작역으로 설정하여 호출 필요 o) void DFS(int cur, int start, int line) { /* 순환선을 찾았을 경우, 기록 후 반환 */ // 현재역 = 시작역 && 최소 2개 이상의 경로를 지난 경우 if (cur == start && line >= 2) { Cycle = true; // 순환선으로 체크 return; // 반환 } /* 순환선을 찾지 못함 */ Visit[cur] = true; // 역 방문 표시 for (int i = 0; i < Station[cur].size(); i++) // 현재 역과 연결된 모든 역을 탐색 { int Next = Station[cur][i]; if (Visit[Next] == false) // 아직 탐색하지 않은 역이라면 { DFS(Next, start, line + 1); // 탐색 시작 } else // 이미 탐색했던 역이라면 { if (Next == start && line >= 2) // Next = start && 2개 이상의 경로를 지났을 때만 { DFS(Next, start, line); // 다음 단계 호출 } } /* 순환선을 찾았을 경우, 최종 반환 */ if (Cycle == true) return; } } /* 가장 가까운 순환선과의 거리 확인 */ int BFS(int a) { memset(Visit, false, sizeof(Visit)); queue<pair<int, int>> Q; Q.push(make_pair(a, 0)); Visit[a] = true; while (!Q.empty()) { int cur = Q.front().first; // 해당 역 int cnt = Q.front().second; // a값과의 거리 Q.pop(); // 탐색 과정에서 순환선에 속해있는 역을 만나게 되면, 바로 거리를 반환시킴. if (Check_Cycle_Station[cur] == true) { return cnt; } for (int i = 0; i < Station[cur].size(); i++) { int Next = Station[cur][i]; if (Visit[Next] == false) { Visit[Next] = true; Q.push(make_pair(Next, cnt + 1)); } } } } /* BFS(순환값찾기) + DFS(순환선과의거리저장) + 출력 */ void Solution() { /* BFS */ for (int i = 1; i <= N; i++) { memset(Visit, false, sizeof(Visit)); Cycle = false; DFS(i, i, 0); if (Cycle == true) { Check_Cycle_Station[i] = true; // 사이클에 포함되어 있는 값이라고 표시 } } /* DFS */ for (int i = 1; i <= N; i++) { if (Check_Cycle_Station[i] == true) // 사이클에 포함되어 잇는 값일 경우 { Answer.push_back(0); // 거리는 0 continue; // 다음 진행 } Answer.push_back(BFS(i)); } /* 출력 */ for (int i = 0; i < Answer.size(); i++) { cout << Answer[i] << " "; } cout << '\n'; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); Input(); Solution(); return 0; }
'문과 코린이의, [C. C++] 기록 > C++ 백준 문제풀이' 카테고리의 다른 글
문과 코린이의 IT 기록장] C++ 백준 문제풀이[그래프 1] - Two Dots (16929) (0) | 2021.11.07 |
---|---|
[문과 코린이의 IT 기록장] C++ 백준 문제풀이[그래프 1] - 나이트의 이동 (7562) (0) | 2021.10.01 |
[문과 코린이의 IT 기록장] C++ 백준 문제풀이[그래프 1] - 토마토 (7576) (0) | 2021.10.01 |
[문과 코린이의 IT 기록장] C++ 백준 문제풀이[그래프 1] - 미로 탐색 (2178) (0) | 2021.09.27 |
[문과 코린이의 IT 기록장] C++ 백준 문제풀이[그래프 1] - 섬의 개수 (4963) (0) | 2021.09.27 |