概念
匹配:即一个边的集合,在这个边的集合中,任意两条边都没有公共顶点
最大就不用多说了吧(
算了还是简单说一下:一个点最多只能有与其有关系的一条边被选中,问最多能选择多少条边
匹配点,匹配边,非匹配点,非匹配边,这四个概念也很好理解吧(
交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路
增广路:一条回到非匹配点的交替路
感性理解一下就好了
匈牙利算法
基于深度优先搜索一遍一遍搜索增广路的存在性来增加匹配对数的
感性理解一下就好了
我们现在用找朋友的例子来模拟一下这个算法:
每个\(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)\) 分别表示左边的点,边的数量,右边的点