본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 2188번 축사 배정 (Java)

#2188 축사 배정

난이도 : 플레 4

유형 : 이분 매칭

 

2188번: 축사 배정

농부 존은 소 축사를 완성하였다. 축사 환경을 쾌적하게 유지하기 위해서, 존은 축사를 M개의 칸으로 구분하고, 한 칸에는 최대 한 마리의 소만 들어가게 계획했다. 첫 주에는 소를 임의 배정해

www.acmicpc.net

▸ 문제

농부 존은 소 축사를 완성하였다. 축사 환경을 쾌적하게 유지하기 위해서, 존은 축사를 M개의 칸으로 구분하고, 한 칸에는 최대 한 마리의 소만 들어가게 계획했다.

첫 주에는 소를 임의 배정해서 축사를 운영했으나, 곧 문제가 발생하게 되었다. 바로 소가 자신이 희망하는 몇 개의 축사 외에는 들어가기를 거부하는 것이다.

농부 존을 도와 최대한 많은 수의 소가 축사에 들어갈 수 있도록 하는 프로그램을 작성하시오. 축사의 번호는 1부터 M까지 매겨져 있다.

 입력

첫째 줄에 소의 수 N과 축사의 수 M이 주어진다. (1 ≤ N, M ≤ 200)

둘째 줄부터 N개의 줄에는 각 소가 들어가기 원하는 축사에 대한 정보가 주어진다. i번째 소가 들어가기 원하는 축사의 수 Si (0 ≤ Si ≤ M)이 먼저 주어지고, 이후 Si개의 축사 번호가 주어진다. 같은 축사 번호가 두 번 이상 주어지는 경우는 없다.

 출력

첫째 줄에 축사에 들어갈 수 있는 소의 최댓값을 출력한다.

 

문제 풀이  

네트워크 플로우와 연계되는 개념인 이분 매칭(Bipartite Matching) 알고리즘 문제이다. 이분 매칭은 이분 그래프에서 최대 매칭을 찾는 문제를 간단하게 이분 매칭이라고 부른다.

  • 그룹 A(소)와 그룹 B(축사)를 주어진 대로 연결하고 용량을 1로 하면 해당 그래프의 최대 유량을 구한 뒤 유량이 흐르는 간선을 모으면 최대 매칭이 된다.

 

시뮬레이션

이분 매칭 구현은 포드-폴커슨 알고리즘처럼 증가 경로를 찾은 뒤 유량을 보내는 것을 반복해주면 된다. 문제에서 주어진 예제를 통해 시뮬레이션을 돌려보자.

 

1번 소 축사 배정하기

  • 1번 소는 (2, 5)에 매칭이 가능하므로, 일단 2번에 매칭한다.

1번 소 매칭하기

 

2번 소 축사 배정하기

  • 1번 소는 (2, 5),
  • 2번 소는 (2, 3, 4)에 매칭이 가능하다.
  • 2번 소가 가장 먼저 도착한 축사는 2이다. 따라서 1번 소가 다른 축사 배정이 가능한지 확인하고 가능하면 배정을 바꿔준다.
  • 1번 소는 5, 2번 소는 2에 매칭한다.

2번 소 매칭하기 (1번 소 재매칭)

 

 

3번 소 축사 배정하기

  • 3번 소는 (1, 5)에 매칭이 가능하므로, 1번에 매칭한다.

3번 소 매칭하기

 

4번 소 축사 배정하기

  • 4번 소는 (1, 2, 5)에 매칭이 가능하다.
  • 1번 매칭? (재귀로 탐색함)
    • 3번 소가 5번에 매칭해야 된다.
    • 그러려면 5번을 점유하고 있는 1번 소가 2번에 매칭해야 된다.
    • 그러면 2번을 점유하고 있는 3번에 매칭해야 된다.
  • 따라서 (1번 소 - 2), (2번 소 - 3), (3번 소 - 5), (4번 소 - 1)로 매칭이 된다.

4번 소 매칭(1,2,3번 소 재매칭)

 

 

5번 소 축사 배정하기

  • 5번 소는 (2)에 매칭이 가능한데, 2번에 매칭하려면 최종적으로 재귀 탐색을 해보면 4번 소가 다른 곳을 매칭해야 되는데 (1, 2, 5)모두 소들이 점유하고 있으므로 재매칭이 안된다.
  • 따라서, 5번 소는 축사를 배정할 수 없다.

 

DFS 탐색으로 축사 배정 로직 만들기

1번 소부터 ~ n번 소까지 순차적으로 탐색하면서, 축사(d)가 비어있거나 그 축사를 점유하고 있는 소가 다른 축사로 들어갈 수 있는 경우에만 현재 소(here)를 nxt 축사에 배정해주면 된다.

static boolean dfs(int here) {
	for(int nxt : list[here]) {
		if(!check[nxt]) {
			check[nxt] = true;
			// 비어있거나 점유 노드에 더 들어갈 공간이 있는 경우 
			if(d[nxt] == 0 || dfs(d[nxt])) {
				d[nxt] = here;
				return true;
			}
		}
	}
	return false;
}

 

 

풀이 코드 

import java.io.*;
import java.util.*;

public class Main {

	static List<Integer>[] list;
	static boolean[] check;
	static int[] d;
	public static void main(String[] args) throws  IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		
		list = new ArrayList[n+1];
		for(int i=1; i<=n; i++) {
			list[i] = new ArrayList<>();
		}
		
		check = new boolean[m+1];
		d = new int[m+1];
		
		for(int i=1; i<=n; i++) {
			st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			for(int j=0; j<s; j++) {
				list[i].add(Integer.parseInt(st.nextToken()));	
			}
		}
		
		int cnt = 0;
		for(int i=1; i<=n; i++) {
			Arrays.fill(check, false);
			if(dfs(i)) cnt++;
		}
		System.out.println(cnt);
	}
	
	static boolean dfs(int here) {
		for(int nxt : list[here]) {
			if(!check[nxt]) {
				check[nxt] = true;
				if(d[nxt] == 0 || dfs(d[nxt])) {
					d[nxt] = here;
					return true;
				}
			}
		}
		return false;
	}
}