应该会持续更新网络流的总结
匈牙利算法的生活解释
有人说它挺暴力的,确实是挺暴力的
这里借用啊哈算法里的一段话,我想没有比这讲的更好动了的
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;
}
严谨的学术性解释
洛谷二分图最大匹配的模板题中的一些问题
- 第一个问题
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 - 这个题数据有坑,v可能大于m,这就是说可能Y集中有一些本不该被考虑入内的不法分子,如果同这些不法分子一起算进去那程序就“过于正确”反而WA
- 为什么就算Y是从头开始编号,不加直接读入也能通过?
这是因为这是一个二分图,而且我们使用的是有向边,这样的话一个点既能作为出发点也能做为终点,作为出发点时它属于X集,作为终点时它属于Y集。
就像power函数一样power(double,double)和power(int,int)调用的是两个不同的函数。 - 一个点在不同条件下属于不同集合接受不同的任务,为什么没有产生混淆
X集和Y集分的很清楚的,X只能作为出发点,Y只能作为终点
作为起点时一定属于X,作为终点时一定属于Y,X不能作为终点,这样一来就刚好被有向边安排的明明白白 - 为什么没有这句话邻接矩阵不会出错邻接表反而出错了?if (v > m)continue;
邻接矩阵没事是因为就算你读入了,超出数组了,这个越界行为不一定给你报错,其次是由于写邻接矩阵的时候dfs里遍历边的主循环是1到m,大于m的给忽略了,相当于写了if (v > m)continue;邻接表出错是因为-》看问题2。
先上ac代码
// P3386[模板]二分图匹配.cpp: 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string.h>
#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不够大!有可能
*/