二分图匹配算法学习笔记

二分图是一种特殊的图模型,它可以分成两组互不相交的子集,子集内部没有任何直接连边。

判断二分图比较简单,直接黑白染色即可,若有没有矛盾,就是二分图,否则不是。

现在给出一些定义:

  • 匹配:给定一个二分图 \(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;
}
posted @ 2025-08-24 16:35  hnczy  阅读(13)  评论(0)    收藏  举报