Bipartite Matching
Bipartite Matching
이분 매칭은 두 그룹간의 각 원소들을 한 번씩만 써서 매칭시켜주는 알고리즘이다.
대개 매칭 수의 최대값을 구하라는 문제가 기본이다.
BOJ 2188 - 축사 배정 문제를 보자.
소 $N$ 마리를 축사 $M$ 에 한 축사에 최대 한마리씩 얼마나 많이 넣을 수 있을지를 구해야 한다. 전형적인 이분 매칭 문제이다.
보통 $O(VE)$ 알고리즘이 쓰이고, 더 빠른 알고리즘으론 Dinic 알고리즘처럼 Level Graph 를 구축해 더 빠른 시간에 돌리는 Hopcroft-Karp $O(E\sqrt V)$ 알고리즘이 있다.
알고리즘 $O(VE)$
알고리즘의 기반은 DFS이다. 집합 $A$와 $B$가 있고 각 원소들이 서로 다른 집합의 원소들과 매칭될 수 있는지 여부를 알 때, 다음과 같이 수행한다.
$A$ 에 있는 원소 $a$ 를 $B$에 있는 원소와 매칭시킬 수 있는지 판단한다고 하자.
$a$ 에서 이어져있는 $B$ 에 있는 원소들을 훑는다고 할때 $B$ 쪽의 원소 $b$ 가 아직 $A$ 에 있는 원소와 매칭이 안되었다면 그것과 매칭시키고 알고리즘을 종료한다.
만약 $b$ 가 $A$ 의 원소와 매칭되어있다면, 그 매칭된 원소 $a’$ 를 $b$ 가 아닌 다른 원소와 매칭시킬 수 있는지 재귀적으로 살펴본다.
만약 $a’$ 가 $b$ 가 아닌 다른 $b’$ 와 매칭에 성공한다면 이제 $b$ 는 $a$ 랑 매칭시켜줄 수 있다.
만약 모든 $b$ 에 대해 매칭이 실패한다면 $a$ 는 어떤 원소와도 매칭시킬 수 없다.
이걸 $A$ 의 모든 원소에 대해서 수행하고 한 원소 $a$ 당 DFS를 돌려야 하므로,
시간복잡도는 $O(VE)$ 이다. ($V$=정점의 개수, $E$=간선의 개수)
구현
struct bipartite {
int N, M;
vvi edges;
vi vis, A, B;
bipartite(int N, int M = -1) : N(N), M(~M ? M : N) {
edges.resize(N);
A.resize(N, -1), B.resize(M, -1), vis.resize(N);
}
void add_edge(int f, int t) { edges[f].pb(t); }
bool dfs(int a) {
vis[a] = 1;
for (int b: edges[a]) {
if (B[b] == -1 || !vis[B[b]] && dfs(B[b])) {
A[a] = b, B[b] = a;
return 1;
}
}
return 0;
}
int match() {
int ret = 0;
for (int i = 0; i < N; i++)
fill(all(vis), 0), ret += dfs(i);
return ret;
}
};
구현은 간단하다. dfs
함수는 위에서 설명한 동작을 그대로 수행하며 $A$ 는 $a\to b$ 로 가는 $b$ 의 번호를 갖고 있고, $B$ 는 $b \to a$ 로 가는 $a$ 의 번호를 갖고있다.
한 번의 DFS마다 $\text{visited}$ 배열을 초기화 시켜줌에 유의한다.
연습 문제
기본 문제이지만, 홀수 정점들과 짝수 정점들에 대한 이분 그래프로 나눈 후 구해주자.
Comments