【总结】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
__EOF__

本文链接:https://www.cnblogs.com/cqbzly/p/17530346.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步