【总结】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+Bj≤wi,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,j∈Ewi,j≤∑i=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) 的和最大,且满足:
- 将 A i + B j = w i , j A_i+B_j=w_{i,j} Ai+Bj=wi,j 的边加入,有完备匹配
- 任意 i , j i,j i,j 满足 A i + B j ≤ w i , j A_i+B_j\leq w_{i,j} Ai+Bj≤wi,j ,此时相等子图的完备匹配一定最小
KM 算法的实现流程保证了 使相等子图构成完备匹配的顶标是存在的。因为每次对 A i ( i ∈ T ) A_i(i\in T) Ai(i∈T) 减少一个整数值 △ \triangle △ ,对 B j ( j ∈ T ) B_j(j\in T) Bj(j∈T) 增大一个整数值 △ \triangle △ ,只有四种情况:
- i ∈ T , j ∈ T i\in T,j\in T i∈T,j∈T ,不变
- i ∉ T , j ∉ T i\notin T,j\notin T i∈/T,j∈/T ,不变
- i ∈ T , j ∉ T i\in T,j\notin T i∈T,j∈/T , A i + B j A_i+B_j Ai+Bj 减小
- i ∉ T , j ∈ T i\notin T,j\in T i∈/T,j∈T , 不可能,因为 i i i 是被 j j j 被动加入交错树,只要 j j j 加入了交错树, i i i 也一定会被加入交错树
我们在所有 i ∈ T , j ∉ T i\in T,j\notin T i∈T,j∈/T 的边 ( i , j ) (i,j) (i,j) 之中,找出最小的 A i + B j − w i , j A_i+B_j-w_{i,j} Ai+Bj−wi,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

浙公网安备 33010602011771号