图论:KM算法
如果,将求二分图的最大匹配的所有匹配边的权重看做1
那么用匈牙利算法求二分图的最大匹配的问题也可以看成求二分图的最大权匹配
如果边权是特例,我们就要使用KM算法来做了
这个算法其实还是比较难的,会用就不错了,更不要说证明了
这里以HDU2255为例,这是一个裸题
在这个题目里面X和Y的size是一样的
然后我们稍微介绍一下这个算法(详细的以后再说吧,目前能力不够)
int n,nx,ny,ans; int linker[maxn],lx[maxn],ly[maxn],slack[maxn],visx[maxn],visy[maxn]; int G[maxn][maxn];
linker记录的是与当前的下标节点(Y中)相连的X节点,lx和ly是节点顶标,slack是Y定点的松弛量函数
邻接矩阵存储
在这里面,如果有的边不存在,设置权重为0,这样图就可以近似看成一个全连接二分图
for(int i=1;i<=nx;i++) { lx[i]=-INF; for(int j=1;j<=ny;j++) { if(G[i][j]>lx[i]) lx[i]=G[i][j]; } }
首先初始化X中节点的节点顶标
就是对于每一个节点,看其所连接的所有的边,将最大权重设置为X节点顶标
然后呢,就是从每个节点开始进行DFS增广
根据情况修改可行顶标
for(int x=1;x<=nx;x++) { for(int i=1;i<=ny;i++) slack[i]=INF; while(1) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(dfs(x)) break; //找到增广路,进入下一个点的增广 //如果失败,需要改变顶标使图中可行边数量增加 //在所有的增广路的x顶标中减去常数d //在所有增广路的Y顶标中增加一个常数d int d=INF; for(int i=1;i<=ny;i++) if(!visy[i]&&d>slack[i]) d=slack[i]; for(int i=1;i<=nx;i++) if(visx[i]) lx[i]-=d; for(int i=1;i<=ny;i++) if(visy[i]) ly[i]+=d; else slack[i]-=d; } }
然后DFS增广部分如下:
int dfs(int x) { visx[x]=1; for(int y=1;y<=ny;y++) { if(visy[y]) continue; int tmp=lx[x]+ly[y]-G[x][y]; if(tmp==0) { visy[y]=1; if(linker[y]==-1||dfs(linker[y])) {linker[y]=x;return 1;} } else if(slack[y]>tmp) slack[y]=tmp; } return 0; }
具体原理先鸽了,以后再说
然后给出完整的实现:
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 const int INF=1000000000; 5 const int maxn=305; 6 int n,nx,ny,ans; 7 int linker[maxn],lx[maxn],ly[maxn],slack[maxn],visx[maxn],visy[maxn]; 8 int G[maxn][maxn]; 9 int dfs(int x) 10 { 11 visx[x]=1; 12 for(int y=1;y<=ny;y++) 13 { 14 if(visy[y]) continue; 15 int tmp=lx[x]+ly[y]-G[x][y]; 16 if(tmp==0) 17 { 18 visy[y]=1; 19 if(linker[y]==-1||dfs(linker[y])) 20 {linker[y]=x;return 1;} 21 } 22 else if(slack[y]>tmp) slack[y]=tmp; 23 } 24 return 0; 25 } 26 int KM() 27 { 28 memset(linker,-1,sizeof(linker)); 29 memset(ly,0,sizeof(ly)); 30 for(int i=1;i<=nx;i++) 31 { 32 lx[i]=-INF; 33 for(int j=1;j<=ny;j++) 34 { 35 if(G[i][j]>lx[i]) lx[i]=G[i][j]; 36 } 37 } 38 for(int x=1;x<=nx;x++) 39 { 40 for(int i=1;i<=ny;i++) slack[i]=INF; 41 while(1) 42 { 43 memset(visx,0,sizeof(visx)); 44 memset(visy,0,sizeof(visy)); 45 if(dfs(x)) break; //找到增广路,进入下一个点的增广 46 //如果失败,需要改变顶标使图中可行边数量增加 47 //在所有的增广路的x顶标中减去常数d 48 //在所有增广路的Y顶标中增加一个常数d 49 int d=INF; 50 for(int i=1;i<=ny;i++) 51 if(!visy[i]&&d>slack[i]) 52 d=slack[i]; 53 for(int i=1;i<=nx;i++) 54 if(visx[i]) lx[i]-=d; 55 for(int i=1;i<=ny;i++) 56 if(visy[i]) ly[i]+=d; 57 else slack[i]-=d; 58 } 59 } 60 int res=0; 61 for(int i=1;i<=ny;i++) 62 if(linker[i]!=-1) 63 res+=G[linker[i]][i]; 64 return res; 65 66 } 67 int main() 68 { 69 while(scanf("%d",&n)==1) 70 { 71 nx=ny=n; 72 for(int i=1;i<=n;i++) 73 for(int j=1;j<=n;j++) 74 scanf("%d",&G[i][j]); 75 ans=KM(); 76 printf("%d\n",ans); 77 } 78 return 0; 79 }