概念

匹配:即一个边的集合,在这个边的集合中,任意两条边都没有公共顶点

最大就不用多说了吧(

算了还是简单说一下:一个点最多只能有与其有关系的一条边被选中,问最多能选择多少条边

匹配点,匹配边,非匹配点,非匹配边,这四个概念也很好理解吧(

交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路

增广路:一条回到非匹配点的交替路

感性理解一下就好了

匈牙利算法

基于深度优先搜索一遍一遍搜索增广路的存在性来增加匹配对数的

感性理解一下就好了

我们现在用找朋友的例子来模拟一下这个算法:

每个\(A\)集合要在\(B\)集合里找到自己的朋友,对于一个人\(a\)遍历每一个和他有关系的人\(b\),如果没有人和\(b\)匹配

就直接让他们两个人匹配,反之,则让和\(b\)匹配的人去找其他人匹配(这样真的好吗

找到了就匹配,没找到就算了

如果已经和\(b\)匹配的人找到了其他可以匹配的人,那就算找到了一条增广路,所以也叫增广路算法

可以发现上面的概念真的什么用,感性理解就好了(

code

注意到每次回去找增广路时,可能会不断重复\(dfs(match[v],tag)\)这一句话

具体可以看下面这张图,\(1\)一开始匹配到了\(3\),然后\(2\)想匹配\(3\),但是发现\(1\)匹配了所以需要\(1\)重新去找

但是\(1\)会去找\(3\),并触发\(dfs(match[v],tag)\),但是我们发现\(match[v]==1\)啊!

[学习笔记] 二分图匹配(匈牙利算法)_搜索

 for(int i=1;i<=n;++i)
	if(dfs(i,i)) ++ans;

为了避免上述事情的发生,我们使用了\(vis\)数组,因为一个点你一次没找到增光路,你再怎么找,还是找不到

但是这样的话,就要在每次dfs前现清空数组,很浪费时间,于是我们使用了\(tag\)表示这次dfs是为谁去匹配的

bool dfs(int u,int tag){
    if(vis[u]==tag) return 0;
    vis[u]=tag;
    for(int i=head[u];i;i=nxt[i]){
	int v=ver[i];
	if(!match[v]||dfs(match[v],tag)){
	    match[v]=u;
	    return 1;
	}
    }
    return 0;
}

复杂度\(\mathcal O(n*e+m)\) 分别表示左边的点,边的数量,右边的点