二分图匹配算法学习笔记
二分图是一种特殊的图模型,它可以分成两组互不相交的子集,子集内部没有任何直接连边。
判断二分图比较简单,直接黑白染色即可,若有没有矛盾,就是二分图,否则不是。
现在给出一些定义:
- 匹配:给定一个二分图 \(G\),在 \(G\) 的一个子图 \(M\) 中,\(M\) 的边集 \({E}\) 中的任意两条边都不依附于同一个顶点,则称 \(M\) 是一个匹配。
- 最大匹配:一个图所有匹配中的边数最多的匹配。
- 交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边...形成的路径叫交替路。
- 增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。
下面两个定义是为匈牙利算法做准备的,这里给出增广路 \(P\) 的一些性质:
-
\(P\) 的路径长度必定为奇数,第一条边和最后一条边都不属于 \(M\)。
-
\(P\) 经过取反操作可以得到一个更大的匹配 \(M’\)。
-
\(M\) 为 \(G\) 的最大匹配当且仅当不存在相对于 \(M\) 的增广路径。
这三条性质是匈牙利算法的重点。
这样的话,我们只需要每次找出增广路,往里面加即可,很容易得出算法的过程:
-
置 \(M\) 为空。
-
找出一条增广路径 \(P\),通过取反操作获得更大的匹配 \(M’\) 代替 \(M\)。
-
重复(2)操作直到找不出增广路径为止。
代码就不难实现了:
bool dfs(int u) {
for(int v=1; v<=n; v++) {
if(visy[v] || !mp[u][v])continue;
visy[v]=1;
if(mat[v]==-1 || dfs(mat[v])) {
mat[v]=u;
return true;
}
}
return false;
}
void KM() {
ans=0;
memset(mat,-1,sizeof mat);
for(int i=1; i<=n; i++) {
memset(visy,0,sizeof visy);
if(dfs(i))ans++;
}
cout<<ans<<endl;
}
有时题目会让我们求最小覆盖数,即一张图用多少条链可以覆盖。此时的答案就是总点数减去最大匹配。
但是题目可能会让边加上边权,构成加权二分图的匹配。这个比没有加权的要难一点(但为什么先发明出来呀),具体的流程就是对二分图两边设定一个可行标 \(lx,ly\) 使得对于每一条边 \(lx_i + ly _j \ge w_{i,j}\) 。每次求增广路的时候更新 \(lx,ly\) ,记下哪些结点被访问那些结点没有被访问。求出 $ d=\min(lx_i+ly_j-w_{i,j} )$ 其中 \(i\) 结点被访问,\(j\) 结点没有被访问。然后调整 \(lx\) 和 \(ly\):对于访问过的 \(x\) 顶点,将它的可行标减去 \(d\),对于所有访问过的 \(y\) 顶点,将它的可行标增加 \(d\) 。修改后的顶标仍是可行顶标,原来的匹配 \(M\) 仍然存在,相等子图中至少出现了一条不属于\(M\) 的边,所以造成 \(M\) 的逐渐增广。
代码如下:
bool dfs(int u){
visx[u]=1;
for(int v=1;v<=n;v++){
if(visy[v])continue;
if(lx[u]+ly[v] == w[u][v]){
visy[v]=1;
if(!mat[v] || dfs(mat[v]))return mat[v]=u;
}
else sl[v]=min(sl[v],lx[u]+ly[v] - w[u][v]);
}
return false;
}
void change(){
int d=inf;
for(int i=1;i<=n;i++)
if(!visy[i])d=min(d,sl[i]);
for(int i=1;i<=n;i++){
if(visx[i])lx[i]-=d;
if(visy[i])ly[i]+=d;
}
}
void KM(){
for(int i=1;i<=n;i++){
ly[i]=mat[i]=0;
lx[i]=-inf;
for(int j=1;j<=n;j++)
lx[i]=max(lx[i],w[i][j]);
//cout<<lx[i]<<endl;
}
for(int i=1;i<=n;i++){
memset(sl,0x3f,sizeof sl);
while(1){
memset(visx,0,sizeof visx);
memset(visy,0,sizeof visy);
if(dfs(i))break;
else change();
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans+=w[mat[i]][i];
cout<<ans<<endl;
}

浙公网安备 33010602011771号