N개의 도시와 그 도시들을 연결하는 N-1개의 도로로 이루어진 도로 네트워크가 있다. 모든 도시는 유일한 경로로 연결되어 있으며, 각 도로의 길이는 입력으로 주어진다.
총 K개의 도시 쌍이 주어질 때, 각 쌍에 대해 두 도시를 연결하는 경로 상에서 가장 짧은 도로의 길이와 가장 긴 도로의 길이를 구하는 프로그램을 작성해야 한다. 이 문제는 트리 구조에서 두 노드 사이의 경로를 탐색하면서, 경로 상의 간선 가중치 중 최소값과 최대값을 빠르게 구하는 것이 핵심이다.
이를 위해 트리에서 두 노드의 최소 공통 조상(LCA, Lowest Common Ancestor)을 찾는 알고리즘과 Sparse Table을 활용하여 쿼리를 효율적으로 처리해야 한다. Binary Lifting 기법을 사용하여 각 노드의 2^k번째 조상을 미리 계산해 두면, 두 노드의 LCA를 O(log N) 시간에 찾을 수 있다.
또한, 각 노드에서 조상 노드까지의 경로 상의 최소 및 최대 간선 가중치를 저장해 두면, 두 노드 사이의 경로에서 최소값과 최대값을 구하는 데에도 O(log N) 시간이 소요된다.
문제 : https://www.acmicpc.net/problem/3176
![]() |
|---|
접근 방식
이 문제를 해결하기 위해 다음과 같은 알고리즘과 자료 구조를 사용하였다.
최소 공통 조상(LCA) 알고리즘: 트리에서 두 노드의 LCA를 찾기 위해 Binary Lifting 기법을 활용하였다. 이를 통해 두 노드의 LCA를 O(log N) 시간에 찾을 수 있다.
Sparse Table: 각 노드의 2^k번째 조상을 미리 계산해 두는 테이블을 구축하였다. 또한, 각 노드에서 조상까지의 경로에서의 최소 및 최대 간선 가중치를 저장하여 쿼리를 효율적으로 처리하였다.
DFS(Depth-First Search): 트리를 탐색하면서 각 노드의 깊이(depth)와 부모(parent)를 기록하였다.
Binary Lifting: 각 노드의 2^k번째 조상을 빠르게 찾기 위해 Binary Lifting을 사용하였다.
이를 통해 각 쿼리에 대해 O(log N)의 시간 복잡도로 답을 구할 수 있으며, 전체 시간 복잡도는 O(N log N + K log N)이다.
C++ 코드와 설명
| |
코드 설명
데이터 입력 및 초기화:
- 도로 정보를 입력받아 인접 리스트
adj에 저장한다. memset함수를 사용하여 배열을 초기화한다.anc배열을-1로 초기화하여 초기 상태를 설정한다.min_t배열은 큰 값으로,max_t배열은0으로 초기화한다.
- 도로 정보를 입력받아 인접 리스트
DFS를 통한 깊이 및 조상 설정:
dfs함수에서 각 노드의 깊이와 부모를 설정하고, 간선의 가중치를min_t와max_t에 저장한다.- 재귀적으로 트리를 탐색하며 정보를 수집한다.
Sparse Table 구축:
- 이중 반복문을 통해 각 노드의
2^k번째 조상을 계산하고, 해당 경로에서의 최소 및 최대 간선 가중치를 갱신한다. anc[v][k],min_t[v][k],max_t[v][k]를 업데이트한다.
- 이중 반복문을 통해 각 노드의
쿼리 처리:
- 입력받은 두 노드의 깊이를 맞추기 위해 깊이가 더 깊은 노드를 위로 올린다.
- 깊이를 맞춘 후, 두 노드가 같아질 때까지
k를 감소시키며 조상을 찾아간다. - 이 과정에서 최소값과 최대값을 갱신한다.
- 최종적으로 LCA의 바로 아래 간선까지 고려하여 결과를 출력한다.
위의 코드는 컴파일 에러 없이 동작하며, 주어진 입력에 대해 올바른 출력을 생성합니다.
C++ without library 코드와 설명
stdio.h와 malloc.h만을 사용하여 표준 라이브러리를 사용하지 않고 구현한 코드이다.
| |
코드 설명
인접 리스트 구현:
- 표준 라이브러리를 사용할 수 없으므로,
Edge구조체와 연결 리스트를 사용하여 인접 리스트를 구현하였다. add_edge함수를 통해 간선을 추가한다.
- 표준 라이브러리를 사용할 수 없으므로,
DFS 구현:
dfs함수에서 재귀적으로 트리를 탐색하며 깊이와 조상을 설정하고, 간선 가중치를 저장한다.
Sparse Table 구축 및 쿼리 처리:
- 최소값과 최대값을 갱신할 때 표준 라이브러리의
min,max함수를 사용할 수 없으므로, 조건문을 사용하여 직접 구현하였다. - 쿼리 처리 로직은 앞서 설명한 것과 동일하다.
- 최소값과 최대값을 갱신할 때 표준 라이브러리의
결론
이 문제는 트리에서 두 노드 사이의 경로에서 최소 및 최대 간선 가중치를 빠르게 구해야 하는 전형적인 LCA 응용 문제이다. Binary Lifting과 Sparse Table을 사용하여 각 노드의 조상 정보를 미리 계산해 두면, 쿼리를 O(log N)에 처리할 수 있다.
이를 통해 시간 복잡도를 효율적으로 관리하여 큰 입력에서도 빠르게 답을 구할 수 있었다. 또한, C++ 표준 라이브러리를 사용하지 않고도 알고리즘을 구현하여 언어의 기본 기능만으로도 충분히 문제를 해결할 수 있음을 확인하였다.
추가적으로, 이와 유사한 유형의 문제를 많이 풀어봄으로써 트리에 대한 이해와 알고리즘 구현 능력을 향상시킬 수 있을 것이다. 최적화 측면에서는 메모리 사용량을 줄이거나 더 효율적인 자료 구조를 활용하는 방안을 고려해 볼 수 있다.
![Featured image of post [Algorithm] C++/Python 백준 3176번 : 도로 네트워크](/post/algorithm/2024-09-23-boj-3176/tmp_wordcloud_hu_1657e3d256726b43.png)

![[Algorithm] C++/Python 백준 1014번 : 컨닝](/post/algorithm/2024-09-23-boj-1014/tmp_wordcloud_hu_900aaa5097cd6695.png)
![[Algorithm] C++/Python 백준 2618번 : 경찰차](/post/algorithm/2024-09-23-boj-2618/tmp_wordcloud_hu_715233211fc11af.png)
![[Algorithm] C++/Python 백준 3176번 : 도로 네트워크](/post/algorithm/2024-09-23-boj-3176/tmp_wordcloud_hu_620b770be27432c3.png)
![[Algorithm] C++/Python 백준 16287번 : Parcel](/post/algorithm/2024-09-20-boj-16287/tmp_wordcloud_hu_cf9116e9512f0c6f.png)
![[Algorithm] C++/Python 백준 17401번 : 일하는 세포](/post/algorithm/2024-09-20-boj-17401/tmp_wordcloud_hu_2ba537e0ff2593f0.png)
![[Algorithm] cpp 백준 11932번: 트리와 K번째 수 - PST+LCA](/post/algorithm/2025-08-14-boj-11932-tree-kth-number-pst-lca-cpp-solution/wordcloud_hu_a44ec252ecd7c8a9.png)
![[Algorithm] cpp 백준 11933번: 공장들](/post/algorithm/2025-08-14-boj-11933-factories-virtual-tree-lca-cpp-solution/wordcloud_hu_12426b454432f618.png)
![[Algorithm] cpp 백준 13896번: Sky Tax](/post/algorithm/2025-08-14-boj-13896-sky-tax-cpp-solution/wordcloud_hu_f4c2be1f31150e26.png)
![[Algorithm] cpp 백준 24272번: 루트 노드가 많은 트리일수록 좋은 트리이다](/post/algorithm/2025-08-14-boj-24272-more-root-nodes-better-tree-cpp-solution/wordcloud_hu_41642d1d3a56b905.png)
![[Algorithm] cpp-python 백준 16993번: 연속합과 쿼리 (세그먼트 트리)](/post/algorithm/2025-09-16-boj-16993-maximum-subarray-queries-segment-tree-cpp-python-solution/wordcloud_hu_789f7bcec4a582b7.png)