二分图最大匹配

应该会持续更新网络流的总结

    • 匈牙利算法的生活解释
    • 核心算法
    • 严谨的学术性解释
    • 洛谷二分图最大匹配的模板题中的一些问题

匈牙利算法的生活解释

有人说它挺暴力的,确实是挺暴力的
这里借用啊哈算法里的一段话,我想没有比这讲的更好动了的
二分图最大匹配_第1张图片
二分图最大匹配_第2张图片
1号认识2’号,所以向2’号"主子"2号发送调换请求,2号调换到3’号,这就代表调换成功啦,于是1号便和它询问的对象在一起了,3号也和他询问的对象在一起了

核心算法

n=左边点个数orX集合大小or男生人数
sum=0;
for(int i=1;i<=n;i++){
	memset(book,0,sizeof(book));
	sum+=dfs(i);
}
int dfs(u){
	for(int i=0;i<G[u].size();i++){
		if(book[i]){
			book[i]=1;
			if(match[i]==0 || dfs(match[i])){
				match[i]=u;
				return 1;
			}
		}
	}
	return 0;
}

严谨的学术性解释

这个我看了觉得写的是真的好,比蓝书好懂不少

洛谷二分图最大匹配的模板题中的一些问题

题目地址

  1. 第一个问题
    X集与Y集合是有交集的
    不是说X是1 2 3,Y是 4 5 6
    而是X是1 2 3,Y也是 1 2 3
    也就是说Y集合的元素序号不是紧接着X的而是从头开始编号
    所以我一开始的方法是在读入Y集合中元素的时候我给他加个n
    即v=read(),v+=n;这样让Y集变成从头开始编号。但这样会超时(邻接矩阵)
    为什么?因为这样一来Y集的范围从[1,m]变成了[n+1,n+m],而我遍历的时候没有考虑到这点,直接写的1到m,这样我实际上遍历的是从[1,n+m]这是一个失误,同时我的book和match数组忘记适应Y而调整大小导致RE
  2. 这个题数据有坑,v可能大于m,这就是说可能Y集中有一些本不该被考虑入内的不法分子,如果同这些不法分子一起算进去那程序就“过于正确”反而WA
  3. 为什么就算Y是从头开始编号,不加直接读入也能通过?
    这是因为这是一个二分图,而且我们使用的是有向边,这样的话一个点既能作为出发点也能做为终点,作为出发点时它属于X集,作为终点时它属于Y集。
    就像power函数一样power(double,double)和power(int,int)调用的是两个不同的函数。
  4. 一个点在不同条件下属于不同集合接受不同的任务,为什么没有产生混淆
    X集和Y集分的很清楚的,X只能作为出发点,Y只能作为终点
    作为起点时一定属于X,作为终点时一定属于Y,X不能作为终点,这样一来就刚好被有向边安排的明明白白
  5. 为什么没有这句话邻接矩阵不会出错邻接表反而出错了?if (v > m)continue;
    邻接矩阵没事是因为就算你读入了,超出数组了,这个越界行为不一定给你报错,其次是由于写邻接矩阵的时候dfs里遍历边的主循环是1到m,大于m的给忽略了,相当于写了if (v > m)continue;邻接表出错是因为-》看问题2。

先上ac代码

// P3386[模板]二分图匹配.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include 
#include 
#include 
#include 
#define Max 1000
#define Max2 1000000
using namespace std;
vector<int> G[Max + 1];
int n, m, e;
int book[Max2 + 1];
int match[Max2 + 1];
int sum = 0;
inline int read() {
	int x = 0, f = 1; char c = getchar();
	while (c > '9' || c < '0') { if (c == '-')f = -1; c = getchar(); }
	while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}
inline int dfs(int u) {
	for (int i = 1; i <= (int)G[u].size() - 1; i++) {
		int v = G[u][i];
		if (v > m)continue;
		if (!book[v]) {
			book[v] = 1;
			if (match[v] == 0 || dfs(match[v])) {
				match[v] = u;
				return 1;
			}
		}
	}
	return 0;
}
int main() {
	n = read(); m = read(); e = read();
	register int u, v;
	for (int i = 1; i <= e; i++) {
		u = read(); v = read();
		//if (v > m)continue;
		if (!G[u].size()) { G[u].push_back(0); }
		G[u].push_back(v);
	}
	for (int i = 1; i <= n; i++) {
		memset(book, 0, sizeof(int)*(Max + 1));
		sum += dfs(i);
	}
	printf("%d", sum);
	return 0;
}
/*
3 3 5
1 1
1 2
2 2
2 3
3 1
*/
//
/*
1.为什么原来的代码普遍超时(邻接矩阵),RE(邻接表)
超时:dfs遍历u点的边的时候,从原来的最多m个点变成了最多n+m个点
RE:book和match数组开小了
2.为什么这样读入数据反而不会出错
这样的话一个点既能作为出发点也能做为终点,作为出发点时它属于X集,作为终点时它属于Y集
就像power函数一样power(double,double)和power(int,int)调用的是两个不同的函数
3.一个点在不同条件下属于不同集合接受不同的任务,为什么没有产生混淆
而有向图刚好能区分他们而且,我们只是做了查询操作,这并不足以发生混淆
X集和Y集分的很清楚的,X只能作为出发点,Y只能作为终点
作为起点时一定属于X,作为终点时一定属于Y,X不能作为终点,这样一来就刚好被有向边安排的明明白白
4.为什么没有这句话邻接矩阵不会出错邻接表反而出错了?if (v > m)continue;
woc为什么邻接矩阵没出事邻接表反而出事了,邻接矩阵e[u][v],v都比m最大值1001大了都跑到数组
外面去了match和book反而没事,邻接矩阵不出事邻接表出事?match不够大?book不够大!有可能
*/

你可能感兴趣的:(algorithms,总结)