좋아요!
이번에는 이분 매칭(Bipartite Matching) 문제를 하나 골라서, 문제를 정리하고, 입력 예제와 함께 자바(Java) 코드 + 한 줄 한 줄 꼼꼼하게 해설을 드릴게요.
여기서 쓸 문제는 유명한 이분 매칭 문제 중 하나인 백준 11375번 열혈강호를 골라보겠습니다.
여러 명의 직원(N명)과 여러 개의 일(M개)이 있다.
각 직원마다 할 수 있는 일이 주어진다. 한 사람은 한 가지 일만 맡을 수 있고, 한 가지 일에는 한 사람만 맡을 수 있다.
이때 할 수 있는 일의 수를 최대화하는 프로그램을 작성해라.
5 5
3 1 2 3
1 1
1 2
1 3
1 5
이 경우 최대로 맡을 수 있는 일 수 = 4개.
4
이 문제는 이분 그래프를 만들 수 있어요. 한쪽은 직원 집합, 다른 쪽은 일 집합.
간선을 "할 수 있는 일"로 이어주고, 최대 매칭을 찾습니다.
→ DFS를 이용해 증대 경로를 찾는 Hungarian Algorithm을 구현!
이제 코드를 볼까요. 코드를 먼저 보여주고, 아래에서 한 줄 한 줄 뜯어서 예제와 함께 설명할게요.
import java.util.*;
public class Main {
static int N, M;
static ArrayList<Integer>[] adj; // 직원별 가능한 일 리스트
static int[] job; // 각 일에 할당된 직원 번호
static boolean[] visited; // dfs용 방문 체크
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt(); // 직원 수
M = sc.nextInt(); // 일 수
adj = new ArrayList[N+1]; // 인덱스 1~N
for (int i = 1; i <= N; i++) {
adj[i] = new ArrayList<>();
int cnt = sc.nextInt();
for (int j = 0; j < cnt; j++) {
int work = sc.nextInt();
adj[i].add(work); // i번째 직원이 work 일 가능
}
}
job = new int[M+1]; // job[j]: j번째 일 맡은 직원 번호(없으면 0)
int ans = 0;
for (int i = 1; i <= N; i++) {
visited = new boolean[M+1]; // dfs마다 초기화
if (dfs(i)) ans++;
}
System.out.println(ans);
}
static boolean dfs(int employee) {
for (int work : adj[employee]) {
if (visited[work]) continue;
visited[work] = true;
// work가 아직 안 배정되었거나 배정된 직원을 다른 일로 보낼 수 있다면
if (job[work] == 0 || dfs(job[work])) {
job[work] = employee;
return true;
}
}
return false;
}
}
static int N, M;
static ArrayList<Integer>[] adj;
static int[] job;
static boolean[] visited;
N, M: 직원 수와 일 수adj: 각 직원마다 가능한 일 리스트job: job[j] = i이면 j번 일은 i번 직원에게 할당됨visited: DFS에서 이미 검사한 일 방지용Scanner sc = new Scanner(System.in);
N = sc.nextInt();
M = sc.nextInt();
예제 5 5 → N=5, M=5
adj = new ArrayList[N+1];
for (int i = 1; i <= N; i++) {
adj[i] = new ArrayList<>();
int cnt = sc.nextInt();
for (int j = 0; j < cnt; j++) {
int work = sc.nextInt();
adj[i].add(work);
}
}
여기서 예제 입력을 읽어요.
이렇게 adj가 채워지면 아래와 같이 보관:
adj[1] = [1,2,3]
adj[2] = [1]
adj[3] = [2]
adj[4] = [3]
adj[5] = [5]
job = new int[M+1]; // 일 1~M의 주인 직원
int ans = 0;
for (int i = 1; i <= N; i++) {
visited = new boolean[M+1]; // 새롭게 초기화
if (dfs(i)) ans++;
}
이 부분은 N명의 직원마다 dfs() 호출.
ans++ = 일 하나 맡을 수 있으면 카운트 증가이 부분이 핵심!
for (int work : adj[employee]) {
if (visited[work]) continue;
visited[work] = true;
if (job[work] == 0 || dfs(job[work])) {
job[work] = employee;
return true;
}
}
i번째 직원이 할 수 있는 일 work에 대해서:
job[work] == 0 → 현재 이 일이 비었으므로 채우고 return true.dfs(job[work]))직원 1 → 일 1i=1
i=2
i=3
i=4
i=5
최종 ans=4
4
✅ 이 문제를 이렇게 풉니다:
job[] 배열로 일의 현재 주인을 추적.visited[] 배열로 한 번의 dfs 호출마다 검사했던 일은 중복 검사 안 함.