본 포스트에서는 백준 15504번 “프로그래밍 대결” 문제를 상세히 분석하고, 이를 해결하기 위한 알고리즘 접근 방식과 함께 C++ 및 Python으로 구현한 최적화 코드를 소개하고자 한다. 문제의 핵심은 참가자들의 실력, 피로도, 경기 제한 횟수를 고려하여 모든 대결에서 발생하는 흥미로움의 총합에서 참가자들의 피로도 총합을 빼, 대회의 재미를 최대화하는 것이다. 단순한 그리디나 완전 탐색으로는 풀기 어려운 문제로, 네트워크 플로우 모델링과 최소 비용 최대 유량(Minimum Cost Maximum Flow, MCMF) 알고리즘을 적용하여 해결할 수 있다.
문제 : https://www.acmicpc.net/problem/15504
문제 설명
백준 15504번 “프로그래밍 대결” 문제는 N명의 참가자가 참가하는 프로그래밍 대회를 모델링한 문제이다. 각 참가자는 고유의 실력 Aᵢ를 보유하고 있으며, 대회는 두 참가자가 일대일로 붙어 진행되는 총 N-1번의 대결로 구성된다. 대결에서는 항상 더 높은 실력을 가진 참가자가 승리하며, 패배한 참가자는 즉시 탈락하여 더 이상 대결에 참여할 수 없게 된다. 이때 최종 우승자는 가장 높은 실력을 보유한 참가자가 된다. 단, 모든 참가자는 제한된 횟수(Lᵢ)만큼만 대결할 수 있으며, Lᵢ는 모든 참가자에 대해 2 이상임에 유의해야 한다.
각 대결에서 발생하는 흥미로움은 대결에 참여한 두 참가자의 실력 값에 대해 Bitwise XOR 연산을 수행한 값으로 결정된다. 반면, 참가자는 대결을 진행할 때마다 일정량(Hᵢ)의 피로도가 쌓이게 된다. 따라서 대회의 전체 재미는 “모든 대결의 흥미로움 합”에서 “모든 참가자들의 피로도 합”을 차감한 값으로 정의된다. 문제의 목표는 주어진 조건을 만족하면서 이 대회의 재미를 최대화하는 경기 진행 순서와 매칭을 찾는 것이다.
문제 해결의 핵심은, 각 참가자들이 반드시 자신보다 높은 실력을 가진 참가자와 한 번 대결해야 한다는 사실이다. 단, 가장 높은 실력을 가진 우승자를 제외한 모든 참가자는 자신보다 낮은 실력을 가진 참가자와도 제한된 횟수 내에서 추가 대결을 치를 수 있다. 이와 같이 각 참가자의 대결 횟수 제한과 피로도, 그리고 각 대결의 흥미도(실력 XOR 연산 결과)가 복합적으로 작용하는 문제는 단순한 탐욕 알고리즘이나 브루트 포스 방식으로는 효율적으로 해결하기 어렵다. 문제를 해결하기 위해서는 참가자들을 실력 순으로 정렬하고, 이를 기반으로 네트워크 플로우 그래프를 구성하여, S(출발점)에서 T(종점)로 총 N-1의 유량이 흐르도록 연결하는 최소 비용 최대 유량 알고리즘을 적용하는 방식으로 접근할 수 있다. 각 간선의 비용은 두 참가자가 대결할 때 소모되는 피로도와 흥미도 차이로 결정되며, 최종적으로 계산된 최소 비용의 부호를 반전하면 최대 대회의 재미를 구할 수 있게 된다.
접근 방식
문제 해결을 위한 접근 방식은 다음과 같다.
참가자 정렬
각 참가자를 실력 Aᵢ에 따라 오름차순으로 정렬한다. 이를 통해, 낮은 실력을 가진 참가자가 반드시 자신보다 높은 실력을 가진 참가자와 대결하도록 그래프를 구성할 수 있다.네트워크 플로우 그래프 구성
그래프는 S(출발점), 왼쪽 파트(낮은 실력 참가자), 오른쪽 파트(높은 실력 참가자), T(종점)로 구성된다.- S에서 왼쪽 파트로는 각 참가자(우승자를 제외한 참가자)당 1의 용량을 갖는 간선을 추가한다.
- 왼쪽 파트에서 오른쪽 파트로는 실력이 낮은 참가자와 높은 참가자 사이에 대결이 가능하도록 간선을 추가한다. 각 간선의 비용은 “(Hᵤ + Hᵥ) - (Aᵤ XOR Aᵥ)”로 정의된다.
- 오른쪽 파트에서 T로는 각 참가자의 대결 제한(L 값)을 반영하여, 우승자는 L번, 그 외 참가자는 L-1번의 대결이 가능하도록 용량을 설정한다.
최소 비용 최대 유량 알고리즘 적용
총 N-1번의 대결(유량)을 흘리며, 다익스트라와 potential 기법을 사용하여 각 단계에서 최소 비용 경로를 찾는다. 이때, 최종 계산된 총 비용은 “모든 대결의 흥미로움 합”과 “모든 참가자들의 피로도 합”의 차이를 음수로 표현한 값이므로, 부호를 반전하면 최대 대회의 재미를 구할 수 있다.결과 출력
최종적으로 구한 최대 대회의 재미를 출력한다.
C++ 코드와 설명
다음은 최적화된 C++ 코드이다. 각 주요 라인마다 주석을 추가하여 코드의 동작을 상세히 설명하였다.
|
|
코드 설명:
- 입력 부분에서 참가자들의 실력, 피로도, 경기 제한 정보를 입력받고, 실력 기준으로 정렬한다.
- 네트워크 플로우 모델을 구성하여 S에서 왼쪽 파트, 왼쪽 파트에서 오른쪽 파트, 그리고 오른쪽 파트에서 T로 간선을 추가한다. 각 간선은 대결의 비용(피로도와 흥미도 차이)을 반영한다.
- 다익스트라와 potential 기법을 사용한 최소 비용 최대 유량 알고리즘으로 총 N-1의 유량을 흘려보내며, 최종 최소 비용을 계산한다.
- 계산된 최소 비용의 부호를 반전하여 최대 대회의 재미를 출력한다.
Python 코드와 설명
다음은 동일한 알고리즘을 Python으로 구현한 코드이다. 코드 내에 각 단계별로 주석을 추가하여 동작을 상세히 설명하였다.
|
|
코드 설명:
- 입력받은 참가자 정보를 바탕으로 실력, 피로도, 경기 제한 정보를 리스트에 저장하고, 실력 기준으로 정렬한다.
- S, 왼쪽 파트, 오른쪽 파트, T로 구성된 네트워크 플로우 그래프를 구축한다. S에서 왼쪽 파트로, 왼쪽 파트에서 오른쪽 파트로, 오른쪽 파트에서 T로 간선을 추가하며, 각 간선의 비용은 두 참가자의 피로도와 XOR 연산 결과를 이용하여 계산된다.
- 최소 비용 최대 유량 알고리즘을 통해 총 N-1번의 대결(유량)을 흘려보내고, 계산된 최소 비용의 부호를 반전하여 최대 대회의 재미를 도출한다.
결론
본 문제는 참가자 간 대결의 흥미와 피로도라는 두 요소가 복합적으로 작용하는 문제로, 단순한 그리디 접근 방식으로는 해결하기 어려운 문제임을 확인할 수 있었다. 문제를 네트워크 플로우 모델로 전환하고, 최소 비용 최대 유량 알고리즘을 적용함으로써 효율적으로 문제를 해결할 수 있었다. C++와 Python 두 가지 언어로 구현한 코드는 각각의 언어적 특성을 잘 활용하여 최적화되었으며, 향후 유사한 문제에 대해 네트워크 플로우를 적용하는 좋은 예시가 될 수 있다. 추가적인 최적화 방안으로는, 입력 크기가 더 큰 경우에 대비한 자료구조 개선이나, potential 갱신 방식의 최적화를 고려할 수 있음을 느꼈다.