【总结】KM算法

二分图的带权最大匹配

KM 算法只能在满足 带权最大匹配一定是完备匹配 的图中正确求解。

交错树:在匈牙利算法中,如果从某个左部节点出发寻找匹配失败,那么在 DFS 的过程中,所有访问过的节点(若干条路径),以及为了访问这些节点而经过的边,共同构成一棵树。这棵树被成为交错树。

顶标:如果任意 i , j i,j i,j 满足 A i + B j ≤ w i , j A_i+B_j\leq w_{i,j} Ai+Bjwi,j ,则把这些整数值 A i , B j A_i,B_j Ai,Bj 成为节点的顶标。

**相等子图:**二分图中所有节点和满足 A i + B j = w i , j A_i+B_j=w_{i,j} Ai+Bj=wi,j 的边构成的子图。

定理:若相等子图中存在完备匹配,则这个完备匹配就是二分图的带权最大匹配。

很好理解,因为任何一组匹配的边权之和都不可能大于所有顶标的和。即 ∑ i , j ∈ E w i , j ≤ ∑ i = 1 N ( A i + B j ) \sum_{i,j\in E}w_{i,j}\leq \sum_{i=1}^N(A_i+B_j) i,jEwi,ji=1N(Ai+Bj) 。集合 E E E 是最大带权匹配中的边。 为了达到这个上限,匹配边就必须是相等子图中的边。

综上所述,KM 算法的思路是:

构造 A i , B j A_i,B_j Ai,Bj ,使 ∑ i = 1 N ( A i + B i ) \sum_{i=1}^N(A_i+B_i) i=1N(Ai+Bi) 的和最大,且满足:

  1. A i + B j = w i , j A_i+B_j=w_{i,j} Ai+Bj=wi,j 的边加入,有完备匹配
  2. 任意 i , j i,j i,j 满足 A i + B j ≤ w i , j A_i+B_j\leq w_{i,j} Ai+Bjwi,j ,此时相等子图的完备匹配一定最小

KM 算法的实现流程保证了 使相等子图构成完备匹配的顶标是存在的。因为每次对 A i ( i ∈ T ) A_i(i\in T) Ai(iT) 减少一个整数值 △ \triangle ,对 B j ( j ∈ T ) B_j(j\in T) Bj(jT) 增大一个整数值 △ \triangle ,只有四种情况:

  1. i ∈ T , j ∈ T i\in T,j\in T iT,jT ,不变
  2. i ∉ T , j ∉ T i\notin T,j\notin T i/T,j/T ,不变
  3. i ∈ T , j ∉ T i\in T,j\notin T iT,j/T A i + B j A_i+B_j Ai+Bj 减小
  4. i ∉ T , j ∈ T i\notin T,j\in T i/T,jT , 不可能,因为 i i i 是被 j j j 被动加入交错树,只要 j j j 加入了交错树, i i i 也一定会被加入交错树

我们在所有 i ∈ T , j ∉ T i\in T,j\notin T iT,j/T 的边 ( i , j ) (i,j) (i,j) 之中,找出最小的 A i + B j − w i , j A_i+B_j-w_{i,j} Ai+Bjwi,j 作为 △ \triangle 的值。(最小是为了保证进入相等子图的边最少,权值和最大)只要原图存在完备匹配,这样的边一定存在。(这也是必须要求存在完备匹配的原因,会增加一部分边到相等子图里,同时是从交错树加入边的唯一可行方式,其他3中情况都不可能在交错树带来新的边)上述方法既不会破坏前提条件,又能保证至少有一条新的边会加入相等子图,使交错树中至少一个左节点能访问到的右部点增多。

时间复杂度 O ( N 4 ) O(N^4) O(N4) O ( N M 2 ) O(NM^2) O(NM2) 。随机数据 O ( N 3 ) O(N^3) O(N3)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=505;
const int INF=0x3f3f3f3f3f3f3f3f;
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
int n,m;
int match[N];
int pre[N];
int la[N],lb[N];
int delta,upd[N];
int W[N][N];
bool vis[N];
void bfs(int root) {
	int px,py=0,yy=0;
	memset(pre,0,sizeof(pre));
	memset(upd,0x3f,sizeof(upd));
	match[py]=root; 
	//px是当前左部节点,py是上一个右部节点,yy是当前遍历的右部节点 
	//最开始只有root在交错树上 
	//默认右部节点0和左部节点匹配 
	//同时在多条增广路中找边! 
	do{
		px=match[py],delta=INF,vis[py]=1;
		for(int i=1;i<=n;i++)
		    if(vis[i]==0) {
		    	if(upd[i]>la[px]+lb[i]-W[px][i])
		    	    upd[i]=la[px]+lb[i]-W[px][i],pre[i]=py;
		    	if(upd[i]<delta) delta=upd[i],yy=i;
			}
		for(int i=0;i<=n;i++)
		    if(vis[i]) la[match[i]]-=delta,lb[i]+=delta;
		    else upd[i]-=delta;
		py=yy;
	}while(match[py]!=0);
	//增广路取反 
	while(py) match[py]=match[pre[py]],py=pre[py];
}
long long KM() {
	//初始并不满足 A_i+B_j>=w_{i,j} 
	for(int i=1;i<=n;i++) match[i]=la[i]=lb[i]=0;
	for(int i=1;i<=n;i++) {
		memset(vis,0,sizeof(vis));
		bfs(i);
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans=ans+W[match[i]][i];
	return ans;
}
signed main() {
	memset(W,-0x3f,sizeof(W));
	n=read(),m=read();
	for(int i=1;i<=m;i++) {
		int x=read(),y=read(),z=read();
		W[x][y]=max(W[x][y],z);
	}
	printf("%lld\n",KM());
	for(int i=1;i<=n;i++) printf("%lld ",match[i]);
}

后记

1.虚点虚边的添加

因为出题人保证了数据存在完备匹配,所以就算不是完全二分图也可以大胆去跑km。

但有些题目需要特判无解情况,即不存在完美匹配;

还有些题目告诉你,就算非完美匹配也无所谓,为了让权值和最大可以允许某些点无匹配而孤独终生。

这类题目就要求选手对km算法拥有比较清晰的理解,那么请你思考一下,分别应该如何应对?

ok揭晓答案。

首先无论哪种情况,我们都要保证左部点个数大于等于右部点个数,通过为右部点添加虚点来实现。

然后,我们要将原本不存在的边连成虚边

单纯为了让结构对称而已。如果之前有完美匹配,那么加入虚边虚点后还是存在完备匹配。

例:HDU-2426


__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530346.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(83)  评论(1编辑  收藏  举报  
点击右上角即可分享
微信分享提示