KM最大权匹配入门训练
来源:互联网 发布:http传输数据 编辑:程序博客网 时间:2024/06/05 17:56
km算法主要还是通过和匈牙利算法一样的dfs不断求增广路。
大致过程可分为初始化可行性顶标、用匈牙利算法求完全匹配、求解失败则调整顶标值继续找、找到完全匹配后求和。
设顶点Xi的顶标为lx[i],顶点Yi的顶标为ly[i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),lx[i]+ly[j]>=w[i,j]始终成立。
KM算法的正确性基于以下定理:
若由二分图中所有满足lx[i]+ly[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。
初始时为了使lx[i]+ly[j]>=w[i,j]恒成立,令lx[i]为所有与顶点Xi关联的边的最大权,ly[j]=0。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边;
然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;
增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d,则对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;
下面分析整个算法的时间复杂度:每次修改后,图中至少会增加一条可行边,故最多增广M次、修改M次就可以找到仅由可行边组成的完全匹配(除非图中不存在完全匹配,这个可以通过预处理得到),故整个算法的时间复杂度为O(M * (N + 一次修改点标的时间))。而一次修改点标的时间取决于计算d值的时间,如果暴力枚举计算,这一步的时间为O(M),优化:可以对每个Y方点设立一个slk值,表示在DFS增广过程中,所有搜到的与该Y方点关联的边的(lx+ly-W)的最小值(这样的边的X方点必然在增广轨中)。每次DFS增广前,将所有Y方点的slk值设为+∞,若增广失败,则取所有不在增广轨中的Y方点的slk值的最小值为d值。这样一次修改点标的时间降为O(N),总时间复杂度降为O(NM)。
需要注意的一点是,在增广过程中需要记下每个X、Y方点是否被遍历到,即lx[i]、ly[j]。因此,在每次增广前(不是对每个X方点增广前)就要将所有lx和ly值清空。
hdu 2255
奔小康赚大钱
//完完全全的模板题#include <cstdio>#include <cstring>#include <cctype>#include <cmath>#include <set>#include <map>#include <list>#include <queue>#include <deque>#include <stack>#include <string>#include <bitset>#include <vector>#include <iostream>#include <algorithm>#define max(a,b) ((a)>(b)?(a):(b))#define mem(a,b) memset(a,b,sizeof(a))#define For(a,l,r) for(int a=l;a<r;a++)using namespace std;typedef long long LL;const LL mod = 9973;const LL inf=0x3f3f3f3f;const double pi=acos(-1);const int N=320;//最大点数const int M=1000020;// 最大边数int nx,ny;int g[N][N];int linker[N],lx[N],ly[N];int slack[N];bool visx[N],visy[N];bool dfs(int x)//寻找增广路{ visx[x]=true; for(int y=0;y<ny;y++) { if(visy[y])continue; int tep=lx[x]+ly[y]-g[x][y]; if(tep==0) { visy[y]=true; if(linker[y]==-1||dfs(linker[y])) { linker[y]=x; return true; } } else if(slack[y]>tep) slack[y]=tep; } return false;}int KM(){ memset(linker,-1,sizeof(linker)); memset(ly,0,sizeof(ly)); for(int i=0;i<nx;i++) { lx[i]=-inf; for(int j=0;j<ny;j++) if(g[i][j]>lx[i]) lx[i]=g[i][j]; } for(int x=0;x<nx;x++) { for(int i=0;i<ny;i++) slack[i]=inf; while(true) { memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if(dfs(x))break; int d=inf;//不断修改顶标直到找到增广路 for(int i=0;i<ny;i++) if(!visy[i]&&d>slack[i]) d=slack[i]; for(int i=0;i<nx;i++) if(visx[i]) lx[i]-=d; for(int i=0;i<ny;i++) { if(visy[i])ly[i]+=d; else slack[i]-=d; } } } int res=0; for(int i=0;i<ny;i++) if(linker[i]!=-1) res+=g[linker[i]][i]; return res;}int main(){ int n; while(scanf("%d",&n)==1) { for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&g[i][j]); nx=ny=n; printf("%d\n",KM()); } return 0;}
hdu 1533
Going Home
求最小权匹配 只需要把边权值取负,输出-KM()即可。
#include <cstdio>#include <cstring>#include <cctype>#include <cmath>#include <set>#include <map>#include <list>#include <queue>#include <deque>#include <stack>#include <string>#include <bitset>#include <vector>#include <iostream>#include <algorithm>#define max(a,b) ((a)>(b)?(a):(b))#define mem(a,b) memset(a,b,sizeof(a))#define F first#define S secondusing namespace std;typedef long long LL;const LL mod = 9973;const LL inf=0x3f3f3f3f;const double pi=acos(-1);const int N=102;//最大点数const int M=1000020;// 最大边数char mapp[N][N]; pair<int,int>px[100],py[100];int nx,ny;int g[N][N];int linker[N],lx[N],ly[N];int slack[N];bool visx[N],visy[N];bool dfs(int x){ visx[x]=true; for(int y=0;y<ny;y++) { if(visy[y])continue; int tep=lx[x]+ly[y]-g[x][y]; if(tep==0) { visy[y]=true; if(linker[y]==-1||dfs(linker[y])) { linker[y]=x; return true; } } else if(slack[y]>tep) slack[y]=tep; } return false;}int KM(){ memset(linker,-1,sizeof(linker)); memset(ly,0,sizeof(ly)); for(int i=0;i<nx;i++) { lx[i]=-inf; for(int j=0;j<ny;j++) if(g[i][j]>lx[i]) lx[i]=g[i][j]; } for(int x=0;x<nx;x++) { for(int i=0;i<ny;i++) slack[i]=inf; while(true) { memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if(dfs(x))break; int d=inf; for(int i=0;i<ny;i++) if(!visy[i]&&d>slack[i]) d=slack[i]; for(int i=0;i<nx;i++) if(visx[i]) lx[i]-=d; for(int i=0;i<ny;i++) { if(visy[i])ly[i]+=d; else slack[i]-=d; } } } int res=0; for(int i=0;i<ny;i++) if(linker[i]!=-1) res+=g[linker[i]][i]; return res;}int main(){#ifdef LOCALHEI freopen("date.in","r",stdin); freopen("date.out","w",stdout);#endif int n,m; while(scanf("%d%d",&n,&m)==2) { if(n==0&&m==0) break; int tx=0,ty=0; for(int i=0;i<n;i++) scanf("%s",mapp[i]); for(int i=0;i<n;i++) for(int j=0;j<m;j++) { if(mapp[i][j]=='H') px[tx++]=make_pair(i,j); else if(mapp[i][j]=='m') py[ty++]=make_pair(i,j); } for(int i=0;i<tx;i++) for(int j=0;j<ty;j++) g[i][j]=-(abs(px[i].F-py[j].F)+abs(px[i].S-py[j].S)); nx=tx; ny=ty; printf("%d\n",-KM()); } return 0;}
hdu1853
Cyclic Tour
找若干个环使边权值之和最小,这个为什么能用二分最大权匹配呢?
可以想象一下若要把所有点都走一遍肯定有n个入度n个出度,而二分匹配刚好是使一个出度指向另一个没有入度的点,这恰好是最大匹配的思想。主意有重边。
#include <cstdio>#include <cstring>#include <cctype>#include <cmath>#include <set>#include <map>#include <list>#include <queue>#include <deque>#include <stack>#include <string>#include <bitset>#include <vector>#include <iostream>#include <algorithm>#define max(a,b) ((a)>(b)?(a):(b))#define mem(a,b) memset(a,b,sizeof(a))#define F first#define S secondusing namespace std;typedef long long LL;const LL mod = 9973;const LL inf=0x3f3f3f3f;const double pi=acos(-1);const int N=102;//最大点数const int M=1000020;// 最大边数int nx,ny;int g[N][N];int linker[N],lx[N],ly[N];int slack[N];bool visx[N],visy[N];bool dfs(int x){ visx[x]=true; for(int y=0;y<ny;y++) { if(visy[y])continue; int tep=lx[x]+ly[y]-g[x][y]; if(tep==0) { visy[y]=true; if(linker[y]==-1||dfs(linker[y])) { linker[y]=x; return true; } } else if(slack[y]>tep) slack[y]=tep; } return false;}int KM(){ memset(linker,-1,sizeof(linker)); memset(ly,0,sizeof(ly)); for(int i=0;i<nx;i++) { lx[i]=-inf; for(int j=0;j<ny;j++) if(g[i][j]>lx[i]) lx[i]=g[i][j]; } for(int x=0;x<nx;x++) { for(int i=0;i<ny;i++) slack[i]=inf; while(true) { memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if(dfs(x))break; int d=inf; for(int i=0;i<ny;i++) if(!visy[i]&&d>slack[i]) d=slack[i]; for(int i=0;i<nx;i++) if(visx[i]) lx[i]-=d; for(int i=0;i<ny;i++) { if(visy[i])ly[i]+=d; else slack[i]-=d; } } } int res=0; for(int i=0;i<ny;i++) { if(linker[i]==-1||g[linker[i]][i]==-inf) return 1; if(linker[i]!=-1) res+=g[linker[i]][i]; } return res;}int main(){#ifdef LOCALHEI freopen("date.in","r",stdin); freopen("date.out","w",stdout);#endif int n,m; while(scanf("%d%d",&n,&m)==2) { for(int i=0;i<n;i++) for(int j=0;j<n;j++) g[i][j]=-inf; int u,v,c; while(m--) { scanf("%d%d%d",&u,&v,&c); u--; v--; if(-c>g[u][v]) g[u][v]=-c; } nx=n; ny=n; printf("%d\n",-KM()); } return 0;}
- KM最大权匹配入门训练
- 最大权二分匹配—KM算法入门 && 模板
- 最大权匹配KM算法的理解
- 【二分图最大权匹配---KM算法】
- 【最大权二分匹配的KM算法】
- 最大权匹配算法(km)
- 二分图最大权匹配 (KM算法)
- 最大权匹配的KM算法
- 二分图最大权匹配-km算法
- 二分图最大权匹配-km算法
- Assignment (HDU 2853 最大权匹配KM)
- hdu3488Tour【二分图最大权匹配 KM】
- 二分图最大权匹配-km算法
- Km算法 二分图最大权匹配
- KM算法(完备匹配下的最大权匹配)
- 求最大权二分匹配的KM算法
- 求二分图最大权匹配的km算法
- 二分图最大权值匹配KM算法
- 设计模式概述
- VS2013 angularjs 智能提示
- 半角和全角转换函数
- USACO-Section 4.2 Job Processing (贪心)
- 三种常用排序算法整理
- KM最大权匹配入门训练
- C++作业4
- 迷宫问题
- Spring 实现远程访问详解——httpclient
- sphinx 原理及实现
- Android开发笔记(八十九)单例模式
- Linux配置bind服务器DNS服务器
- NGUI的ScrollView平滑移动到指定位置
- Hibert变换的C语言及数组的大小