알고리즘/이분 매칭

BOJ 백준 11378 열혈강호 4

kyjdummy 2025. 5. 21. 21:51

문제 : https://www.acmicpc.net/problem/11378

[ 문제 요약 ]

  • 직원 N 명, M 개의 일이 주어집니다.
  • 벌점 X 점 받은 사람은 일을 최대 X + 1개까지 담당할 수 있습니다.
  • 각 직원은 자신이 받은 벌점은 모르고, 직원이 받은 벌점의 합 K만을 알고 있습니다. 벌점을 각 사람에게 적절히 분배해 일을 최대로 하게 할 때, 할 수 있는 일의 최댓값을 출력합니다.

 

[ 테스트 케이스 설명 ]

5 5 1// 직원 수(1<=1,000), 일의 수(1<=1,000), 벌점의 합(1<=사람수)
5 1 2 3 4 5 // N개 줄에 i번 직원이 할 수 있는 일의 개수와 그 일의 번호가 주어짐
1 1
1 1
1 1
2 1 5
최대 가능한 일 : 4

 

 

[ 알고리즘 분류 ]

  • 최대 유량
  • 이분 매칭

 

[ 문제 해설 ]

 

이분 매칭 문제로, 확실한 것은 일을 많이 할 수 있는 직원이 많은 점수를 받아야 합니다.

 

그러나 단순히 이렇게 일이 많은 직원 순으로 벌점을 더 주면 반례가 생깁니다. 일을 적게 할 수 있는 직원이지만 유일하게 처리 가능한 일이 있을 때, 이 직원에게 벌점을 더 줘야 하기 때문입니다.

 

처음부터 벌점을 직원들에게 나눠 주는 게 아니라, 반대로 이분 매칭을 돌린 후 처리 가능한 일이 있는 직원에게만 벌점을 주는 방식으로 진행합니다.

 

먼저 각 직원을 순회하며 할 수 있는 일을 카운팅 합니다.

 

그 후 추가로 일 처리가 가능한 직원에게 K를 배분하는 방식으로 짭니다. 매칭이 실패하면 다음 번호로 넘어가는 겁니다.

[ 정답 코드 ]

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

class Main{
	
	static int N, M, K;
	static int time;
	static int match[];
	static int visitTime[];
	static List<Integer> adList[];
	
	public static void main(String[] args)throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		N = Integer.parseInt(st.nextToken());// 직원 수(1<=1,000)
		M = Integer.parseInt(st.nextToken());// 일의 수(1<=1,000)
		K = Integer.parseInt(st.nextToken());// 벌점의 합(1<=사람수)
		match = new int[M + 1];
		visitTime = new int[M + 1];
		adList = new ArrayList[N + 1];
		
		for(int i=1; i<=N; i++)
		{
			st = new StringTokenizer(br.readLine());
			int cnt = Integer.parseInt(st.nextToken());
			adList[i] = new ArrayList<>();
			for(int j=0; j<cnt; j++)
				adList[i].add(Integer.parseInt(st.nextToken()));
		}
		
		int jobCnt = 0;
		// 먼저 모든 직원에 대해 이분 매칭을 돌려 할 수 있는 일의 수를 구한다.
		for(int i=1; i<=N; i++)
		{
			++time;
			if(dfs(i))
				++jobCnt;
		}
		
		++time;// 다음 visit을 판단하기 위해 dfs전 +1을 미리 해줌
		// K번 더 매칭을 위해 직원마다 매칭이 불가할 때 까지 이분 매칭을 돌린다.
		for(int i=1; i<=N && K != 0; i++)
		{
			while(dfs(i) && K != 0)
			{// 매칭이 가능하면 계속 일을 매칭시켜 K를 감소시킴
				++jobCnt;
				++time;
				--K;
			}
		}
		
		System.out.print(jobCnt);
	}
	static boolean dfs(int person)
	{
		for(int job : adList[person])
		{
			if(visitTime[job] == time)
				continue;
			
			visitTime[job] = time;
			
			if(match[job] == 0 || dfs(match[job]))
			{
				match[job] = person;
				return true;
			}
		}
		
		return false;
	}
}