일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 서비스 디스커버리
- 스택
- 이분 매칭
- 메모이제이션
- dp
- 완전 탐색
- spring cloud
- BFS
- 이분 탐색
- spring boot
- 구간 트리
- 스프링 시큐리티
- Java
- Zuul
- 플로이드 와샬
- ZuulFilter
- 도커
- 게이트웨이
- 구현
- 달팽이
- 트리
- Gradle
- 유레카
- Spring Cloud Config
- 다익스트라
- 비트마스킹
- 주울
- 백트래킹
- docker-compose
- Logback
- Today
- Total
Hello, Freakin world!
[백준 2188번] 축사 배정 - 이분 매칭 고찰하기 본문
2188번: 축사 배정
농부 존은 소 축사를 완성하였다. 축사 환경을 쾌적하게 유지하기 위해서, 존은 축사를 M개의 칸으로 구분하고, 한 칸에는 최대 한 마리의 소만 들어가게 계획했다. 첫 주에는 소를 임의 배정해�
www.acmicpc.net
이분 매칭 알고리즘을 구현하기만 하면 통과하는 문제라 문제 풀이보단
이분 매칭에 대한 고찰 몇 가지를 남기려 합니다.
아래는 정답 코드입니다.
...
/*
백준 2188번 - 축사 배정
https://www.acmicpc.net/problem/2188
*/
public class Main {
static int[][] edges; // 간선 [cow][barn]
static int[] aMatch, bMatch;
static boolean[] visited;
static int N,M;
public static void main(String[] args) throws IOException {
// BufferedReader br = new BufferedReader(new FileReader("testcase.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken()); M = Integer.parseInt(st.nextToken());
edges = new int[N][M];
for (int cowNum = 0; cowNum < N; cowNum++) {
st = new StringTokenizer(br.readLine());
int barnSize = Integer.parseInt(st.nextToken());
for (int i = 0; i < barnSize; i++) {
int barnNum = Integer.parseInt(st.nextToken())-1;
edges[cowNum][barnNum] = 1;
}
}
System.out.println(bipartiteMatching());
}
// 집합 A의 정점 a에서 B의 매칭되지 않은 정점으로 가는 경로를 찾는다
public static boolean dfs(int a) {
if(visited[a]) return false; // 재방문 크트. 매칭 수정시 이 한 줄의 코드가 어떻게 작용하는지 유의할 것.
visited[a] = true;
for(int b = 0; b < M; ++b) {
if(edges[a][b] == 1) {
//b가 아직 매칭되지 않았다면
if(bMatch[b] == -1) {
aMatch[a] = b;
bMatch[b] = a;
return true;
}
//b가 이미 매칭되어있다면 bMatch[b]에서부터 시작해 경로를 찾는다. 찾았을 경우 재귀 호출한 부분에서 경로를 수정한다.
if(dfs(bMatch[b])) {
//dfs 호출(재귀호출)에서 경로를 수정했으니 b에 연결된 정점이 없으므로 연결해준다.
aMatch[a] = b;
bMatch[b] = a;
return true;
}
}
}
return false;
}
public static int bipartiteMatching() {
aMatch = new int[N]; Arrays.fill(aMatch, -1);
bMatch = new int[M]; Arrays.fill(bMatch, -1);
int size = 0;
for (int start = 0; start < N; start++) {
visited = new boolean[N];
// 증가 경로, 즉 매칭을 찾은 경우 1의 유량 증가시킨다. 증가 경로를 찾을때 마다 +1 ? 정당성 증명 필요하다.
if(dfs(start)) ++size;
}
return size;
}
}
구현은 dfs 방식으로 구현했고 음의 간선 같은 것들이 코드 상에는 나타나지 않지만, 에드몬드 카프 알고리즘과 원리는 동일합니다.
처음에 에드몬드 카프 알고리즘과 동일한 원리나는 것을 몰라서 아래의 코드가 왜 정답을 내는지 이해를 못했습니다.
int size = 0;
for (int start = 0; start < N; start++) {
visited = new boolean[N];
// 증가 경로, 즉 매칭을 찾은 경우 1의 유량 증가시킨다. 증가 경로를 찾을때 마다 +1 ? 정당성 증명 필요하다.
if(dfs(start)) ++size;
}
return size;
위 코드처럼 순차적으로 dfs하면 반드시 최대 유량(매칭)이 나타난다는 걸 어떻게 증명할 수 있을까?
위 물음에 대해 에드몬드 카프 알고리즘에서는 음의 유량 개념을 이용해 해결합니다. 그런데 위 코드에서 음의 유량같은 개념이 안보이더군요. 그러면 간선의 탐색 순서에 따라 최대 유량이 변하는 케이스가 존재해야 됩니다. 하지만 위 코드에서는 그냥 단순히 순서대로 증가경로를 찾아 유량을 더해주고 있습니다. 제가 모르는 뭔가가 있는것 같네요.
결론만 말하자면 아래의 코드가 음의 유량 역할을 대신하고 있습니다.
//b가 이미 매칭되어있다면 bMatch[b]에서부터 시작해 경로를 찾는다. 찾았을 경우 재귀 호출한 부분에서 경로를 수정한다.
if(dfs(bMatch[b])) {
//dfs 호출(재귀호출)에서 경로를 수정했으니 b에 연결된 정점이 없으므로 연결해준다.
aMatch[a] = b;
bMatch[b] = a;
return true;
}
만약 이 문제를 에드몬드 카프 알고리즘으로 풀었다면 b가 매칭된 경우, 음의 유량을 이용해 b와 매칭된 a로 거슬러 올라가 경로를 찾기 시작합니다. 위 방식은 음의 유량을 더하는 대신 재귀를 이용해 그 문제를 해결합니다.
'알고리즘 > PS' 카테고리의 다른 글
[백준 11376번] 열혈강호2 (0) | 2020.09.03 |
---|---|
[백준 11375번] 열혈 강호 (0) | 2020.09.03 |
[백준 1003번] Contact (0) | 2020.08.25 |
[백준 1012] 유기농 배추 (0) | 2020.08.24 |
[백준 1010번] 다리 놓기 (0) | 2020.08.23 |