欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

二分图的最佳匹配

程序员文章站 2022-06-09 20:28:43
...

KM算法了解的头皮发麻

今天从10点半开始了解这个破东西了,一直到下午3点才开始动手敲代码,带着脑袋里残留的印象赶紧写一下心得,废话不多说直接给例题。
hdu 2255 戳这里
这道题我超了一万年的时,细节很重要。
首先什么是KM算法?
要了解这个首先要了解一下很有名的匈牙利算法(这个就不科普了,不知道一定要去百度,代码不长,后面我会贴出来个人感觉最好懂的写法)不然学不了KM算法。KM算法就是为了更快的求最大权匹配。(知道这个就行了)
总体思路
就是不断的找最大边,如果出现了“第三者”怎么办?,这个时候就要顶标数组来修改信息,增加图的选边,找次小的权值。那顶标是什么?事实上在读图以后每个节点都由顶标记录着一个值,这个值在没操作之前是这样的(每个点上面的值就是顶标记录的值):
二分图的最佳匹配
这里我们还要引入相等子图的概念(如果左右任意两个点的顶标值的和等于他们所在边的权值就能满足这个条件),KM算法的核心就是通过不停修改顶标的值构成新的相等子图来实现最大权的匹配。比如上图A会选择D,因为权最大是15,而B他也想选和他权最大的D,但是D已经被A选了,这个时候我们就可以来修改顶标值了!!!首先找到对于A权值次小的路径(对,就是A-E),然后我们用A的顶标值 加上 E的顶标值 减去所在路径的权值 我们把这个值设为d 然后 原边 和 需要加入子图的边 左边都减去d,右边都加上d。执行完这个操作新边就进来了,你可以自己手动算一下。只要反复执行这个操作最后就能得到想要的答案。
KM算法需要什么?

  1. 首先需要保存带权图的信息的二维数组(其实别的也行)map1【】【】。
  2. 然后需要两个记录是否访问的标记数组visx【】和visy【】。
  3. 一个记录连接节点的f【】数组。
  4. 需要设置两个顶标数组lx【】和ly【】【】这个是核心
    看起来很麻烦,但是敲起来思路很清晰。
    **代码来了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
#include<iomanip>
#define MAXN 500
using namespace std;
int map1[MAXN][MAXN];/*图信息*/ 
int lx[MAXN],ly[MAXN];/*顶标信息*/ 
int visx[MAXN],visy[MAXN];/*确认访问与否*/ 
int f[MAXN];/*记住自己的连接节点*/ 
int n;
int find(int x)
{
    visx[x]=1;
    for(int i=1;i<=n;i++)
    {
        if((map1[x][i]==lx[x]+ly[i])&&!visy[i])   /*在相等子图里才搜索*/
        {
            visy[i]=1;
            if(!f[i]||find(f[i])) /*如果没有连接节点就直接赋值,不然进入递归*/
            {
                f[i]=x;
                return 1;
            }
        }
    }
    return 0;
    /*匈牙利算法找增广路径*/ 
}
int km()
{   
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        while(1)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(find(i))  /*如果能够顺利的通过find函数就说明不需要增加路径,反之如果失败,就要一直增加选边一直到成功为止。*/
            break;  
            int d=100000000;
            for(int j=1;j<=n;j++)
             if(visx[j])       /*如果该节点访问过了,说明需要增加边*/
              for(int k=1;k<=n;k++)
               if(!visy[k]&&d>(lx[j]+ly[k]-map1[j][k])) /*从没有搜索过的节点里选一条次长边*/
               d=lx[j]+ly[k]-map1[j][k];
            for(int j=1;j<=n;j++)
            {
                if(visx[j])
                    lx[j]-=d;
                if(visy[j])
                    ly[j]+=d;
            }           
        }
    }
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        if(f[i])
        sum+=map1[f[i]][i];
     } 
     return sum;
}
int main()
{
   while(~scanf("%d",&n))
   {    
        for(int i=1;i<=n;i++)
        {      int maxd=-1;
            for(int j=1;j<=n;j++)
            {
                scanf("%d",&map1[i][j]);
                maxd=max(maxd,map1[i][j]);
            }
            lx[i]=maxd;
        } /*读图且初始化顶标的值*/ 
        printf("%d\n",km());
   }
}

记住好吗!!!!!!