RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了。
可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个男生做partner和她同坐。
但是,每个女孩都有各自的想法,举个例子把,
考虑到经费问题,boss刘决定只让找到partner的人去坐过山车,其他的人,嘿嘿,就站在下面看着吧。
聪明的Acmer,你可以帮忙算算最多有多少对组合可以坐上过山车吗?
输入数据的第一行是三个整数K , M , N,分别表示可能的组合数目,女生的人数,男生的人数。
接下来的K行,每行有两个数,分别表示女生 Ai 愿意和男生 Bj 做partner。
最后一个0结束输入。
对于每组数据,输出一个整数,表示可以坐上过山车的最多组合数。
输入 | 6 3 3 1 1 1 2 1 3 2 1 2 3 3 1 0 |
输出 | 3 |
本题是二分图最大匹配问题。
首先,我们需要知道什么是二分图?
二分图是一种特殊的图结构,二分图中的节点可以分为两部分,每个部分中的节点之间互不相连,比如下图所示:
绿色点集是一个部分,橙色点集是一个部分,各个部分中节点之间互不相连
二分图的 "匹配" 指的是 "边的集合“,"匹配"中的各个边之间没有共同节点,即每个边都是独立的。
比如下图中的两个红色边就形成了一个匹配,因为1-4边,2-5边之间没有共同节点,两个边是互相独立的。
二分图的最大匹配,指的是,边最多的"匹配",比如下图中,1-5边,2-6边,3-4边之间互相独立,可以形成一个数量为3条边的匹配。下面二分图最多只能有3条边的匹配。
那么,如何求二分图的最大匹配呢?
最常用的办法就是匈牙利算法。
在解释匈牙利算法之前,我们需要先对二分图最大匹配问题的实际意义做一个解释。
比如现在有男生若干,女生若干,而每个男生都有心仪的多个女生,每个女生也有心仪的多个男生,而男生、女生只会和心仪的对象进行配对,现在需要实现最大配对?
上面例子就是二分图最大匹配的典型应用。
我们假设节点1,2,3是男生,4,5,6是女生,那么初始时,存在如下心仪关系:
因此可得二分图如下:
下面开始演示匈牙利算法来找到最大匹配数:
我们需要选择二分图的一部分作为发起配对请求的一方,比如我们选择男生作为发起配对请求的一方,此时遍历每一个男生:
假设先遍历出男生1,而男生1和与女生4,女生5互相心仪,此时我们可以任选一个女生进行配对,比如选择女生4进行配对,由于女生4当前没有配对对象,因此男生1和女生4可以配对成功
男生1完成配对后,继续遍历下一个男生2发起配对请求,而男生2和女生5,女生6互相心仪,此时我们可以任选一个女生进行配对,比如选择女生5进行配对,由于女生5当前没有配对对象,因此男生2和女生5可以配对成功
男生2完成配对后,继续遍历下一个男生3发起配对请求,而男生3只和女生4互相心仪,但是女生4已经和男生1配对了!!!
此时,为了实现最大配对,我们只能委屈男生1,让他找一找其他可以配对的女生,然后发现男生1还和女生5互相心仪,因此我们尝试将男生1和女生5配对。
但是女生5已经和男生2发生配对了!!!
因此,为了实现最大配对,只能委屈男生2找找看其他可以配对的女生,然后发现男生2还和女生6互相心仪,因此尝试将那是2和女生6进行配对
而女生6没有发生过配对,因此男生2可以和女生6成功配对。
由于男生2和女生6成功配对了,因此虚线2-6变为实线,而一个男生只能和一个女生配对,因此实线2-5变为虚线
而由于女生5和男生2解除了配对状态,因此男生1和女生5就可以成功配对,因此1-5从虚线变为实线,而一个男生只能和一个女生配对,因此实线1-4变为虚线
由于女生4和男生1解除了配对状态,因此男生3和女生4可以成功配对,因此虚线3-4变为实线
此时我们完成了所有男生的配对,即得到了最大匹配。
上面,从配对情况图中,黑虚线和红实线,逆变为,黑实线和红虚线,实现了配对数+1,
如下图左边只有两个配对(红色线),右边有三个配对(黑色线)
我们将左边这种展开为“虚实虚实虚”的情况,称为增广路,增广路的特点是两端为虚,中间虚实交替,增广路的逆变,可以实现配对数+1。
关于增广路的定义和意义就是如此,了解即可。
以上就是匈牙利算法的实现过程。总结一下就是:
初始时,寻找一方作为配对发起方,比如男生,遍历每一个男生发起配对请求:
男生a只能对互相心仪的女生发起配对请求,
按照上面逻辑我们让每一个男生都发起配对请求,最终我们可以找到最大匹配数。
更多细节请看下面代码实现。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int m;
static int n;
static boolean[][] edges;
static int[] match;
static boolean[] vis;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNextInt()) {
int k = sc.nextInt();
if (k == 0) break;
m = sc.nextInt(); // m个女生
n = sc.nextInt(); // n个男生
// edges[a][b] == true 表示 a女生 和 b男生 可以配对
edges = new boolean[m + 1][n + 1];
for (int i = 0; i < k; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
edges[a][b] = true;
}
// match[b] 表示男生b配对的女生
match = new int[n + 1];
// vis[b] 表示男生b是否在本次增广路中
vis = new boolean[n + 1];
// ans记录题解
int ans = 0;
// 遍历女生a
for (int a = 1; a <= m; a++) {
// 从女生a开始进行增广路探索,探索前,需要将vis重置
Arrays.fill(vis, false);
// 如果a找到配对男生,则配对边+1
if (dfs(a)) ans++;
}
System.out.println(ans);
}
}
public static boolean dfs(int a) {
// 遍历男生b
for (int b = 1; b <= n; b++) {
// 如果男生b不在女生a发起的探索的增广路中,且a,b可以配对
if (!vis[b] && edges[a][b]) {
// 则当前增广路加入男生b
vis[b] = true;
// 如果男生b没有被其他人配对 || 已经和其他人配对,但是男生b当前配对的女生match[b]可以放弃男生b,而和其他男生配对
if (match[b] == 0 || dfs(match[b])) {
// 则男生b可以和女生a配对,即配对成功,match[b] = a
match[b] = a;
return true;
}
}
}
return false;
}
}
?
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
// m个女生, n个男生, k条边
const [k, m, n] = (await readline()).split(" ").map(Number);
// edges[a][b] == true 表示 a女生 和 b男生 可以配对
const edges = new Array(m + 1)
.fill(0)
.map(() => new Array(n + 1).fill(false));
for (let i = 0; i < k; i++) {
const [a, b] = (await readline()).split(" ").map(Number);
edges[a][b] = true;
}
await readline(); // 读取最后一行输入的0
// match[b] 表示男生b配对的女生
const match = new Array(n + 1).fill(0);
// vis[b] 表示男生b是否在本次增广路中
const vis = new Array(n + 1).fill(false);
// ans记录题解
let ans = 0;
// 遍历女生a
for (let a = 1; a <= m; a++) {
// 从女生a开始进行增广路探索,探索前,需要将vis重置
vis.fill(false);
// 如果a找到配对男生,则配对边+1
if (dfs(a)) ans++;
}
function dfs(a) {
// 遍历男生b
for (let b = 1; b <= n; b++) {
// 如果男生b不在女生a发起的探索的增广路中,且a,b可以配对
if (!vis[b] && edges[a][b]) {
// 则当前增广路加入男生b
vis[b] = true;
// 如果男生b没有被其他人配对 || 已经和其他人配对,但是男生b当前配对的女生match[b]可以放弃男生b,而和其他男生配对
if (match[b] == 0 || dfs(match[b])) {
// 则男生b可以和女生a配对,即配对成功,match[b] = a
match[b] = a;
return true;
}
}
}
return false;
}
console.log(ans);
})();
?
# 输入获取
k, m, n = map(int, input().split()) # k个配对, m个女生, n个男生
edges = [[False] * (n + 1) for _ in range(m + 1)]
for _ in range(k):
a, b = map(int, input().split())
edges[a][b] = True # edges[a][b] == true 表示 a女生 和 b男生 可以配对
input()
match = [0] * (n + 1) # match[b] 表示男生b确定配对的女生
def dfs(a, vis):
# 遍历男生b
for b in range(1, n + 1):
# 如果男生b不在女生a发起的探索的增广路中,且a,b可以配对
if not vis[b] and edges[a][b]:
# 则当前增广路加入男生b
vis[b] = True
# 如果男生b没有被其他人配对 || 已经和其他人配对,但是男生b当前配对的女生match[b]可以放弃男生b,而和其他男生配对
if match[b] == 0 or dfs(match[b], vis):
# 则男生b可以和女生a配对,即配对成功,match[b] = a
match[b] = a
return True
return False
# 算法入口
def getResult():
ans = 0
for a in range(1, m + 1):
# vis[b] 表示男生b是否在a女生发起的增广路探索中
vis = [False] * (n + 1)
# 如果a找到配对男生,则配对边+1
if dfs(a, vis):
ans += 1
return ans
# 算法调用
print(getResult())
#include <stdio.h>
#define MAX_SIZE 501
#define TRUE 1
#define FALSE 0
int m, n; // m个女生, n个男生
int edges[MAX_SIZE][MAX_SIZE]; // edges[a][b] == true 表示 a女生 和 b男生 可以配对
int match[MAX_SIZE]; // match[b] 表示男生b配对的女生
int vis[MAX_SIZE]; // vis[b] 表示男生b是否在本次增广路中
int dfs(int a) {
// 遍历男生b
for (int b = 1; b <= n; b++) {
// 如果男生b不在女生a发起的探索的增广路中,且a,b可以配对
if (!vis[b] && edges[a][b]) {
// 则当前增广路加入男生b
vis[b] = 1;
// 如果男生b没有被其他人配对 || 已经和其他人配对,但是男生b当前配对的女生match[b]可以放弃男生b,而和其他男生配对
if (match[b] == 0 || dfs(match[b])) {
// 则男生b可以和女生a配对,即配对成功,match[b] = a
match[b] = a;
return TRUE;
}
}
}
return FALSE;
}
int main() {
while (1) {
int k;
scanf("%d", &k);
if (k == 0) break;
scanf("%d %d", &m, &n);
// 初始化edges
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
edges[i][j] = FALSE;
}
}
// 关联可匹配的男女生
for (int i = 0; i < k; i++) {
int a, b;
scanf("%d %d", &a, &b);
edges[a][b] = TRUE;
}
// 初始化match
for (int i = 0; i <= n; i++) {
match[i] = 0;
}
// ans记录题解
int ans = 0;
// 遍历女生
for (int a = 1; a <= m; a++) {
// 初始化vis
for (int i = 0; i <= n; i++) {
vis[i] = FALSE;
}
// 如果女生a找到匹配男生,则匹配边++
if (dfs(a)) {
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}