km算法

今天花了些时间学了下km算法 看了下代码有点大概思路,还是要多做题;


Kuhn-Munkras算法流程:
  (1)初始化可行顶标的值
  (2)用匈牙利算法寻找完备匹配
  (3)若未找到完备匹配则修改可行顶标的值
  (4)重复(2)(3)直到找到相等子图的完备匹配为止


引用:
KM算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。
设顶点Xi的顶标为A[i],顶点Yi的顶标为B [i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),A[i]+B[j]>=w[i,j]始终 成立。

KM算法的正确性基于以下定理:
  若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
  这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;
如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。
  初始时为了使A[i]+B[j]>=w[i,j]恒成立,令A[i]为所有与顶点Xi关联的边的最大权,B[j]=0。如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,
直到相等子图具有完备匹配为止。
  我们求当前相等子图的完备匹配失败了,是因为对于某个X顶点,我们找不到一条从它出发的交错路。
这时我们获得了一棵交错树,它的叶子结点全部是X顶点。现在我们把交错树中X顶点的顶标全都减小某个值d,Y顶点的顶标全都增加同一个值d,那么我们会发现:
两端都在交错树中的边(i,j),A[i]+B[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
两端都不在交错树中的边(i,j),A[i]和B[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
X端不在交错树中,Y端在交错树中的边(i,j),它的A[i]+B[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。
X端在交错树中,Y端不在交错树中的边(i,j),它的A[i]+B[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
  现在的问题就是求d值了。为了使A[i]+B[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,d应该等于min{A[i]+B[j]-w[i,j]|Xi在交错树中,Yi不在交错树中}。
  以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶 标,每次修改顶标时由于要枚举边来求d值,
复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数 slack,每次开始找增广路时初始化为无穷大。
在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与A [i]+B[j]-w[i,j]的较小值。
这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改 顶标后,要把所有的slack值都减去d。
最后把值加起来就ok。

//KM算法求最小权二分匹配,模板题,构图很简单,直接把人当作左边的点,房子当作右边的点,
//两者之间的曼哈顿距离当作权值即可。第一次搞带权二分匹配的题,就是用KM算法求最小权的时候要加个处,由于KM求的是最大权,
//所以在套模板之前把权值都取下相反值最后再把KM算法求出来的最大权值取反即可。
//poj2195 km算法
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define INF 99999999
char map[103][103];
int n,m,g[103][103],visr[103],visl[103],pr[103],pl[103],match[103],num,slack[103];
struct node
{
    int x;
    int y;
}mm[103],hh[103];
int dfs(int u)
{
    int i,j,val;
    visl[u]=1;
    for(i=0;i<num;i++)
    {
        if(!visr[i])
        {
            val=pl[u]+pr[i]-g[u][i];
            if(val==0)//即满足匹配条件
            {
                visr[i]=1;
                if(match[i]==-1||dfs(match[i]))
                {
                    match[i]=u;
                    return 1;
                }
            }
            if(val>0&&val<slack[i])
                slack[i]=val;
        }
    }
    return 0;
}
int km()
{
    int i,j,res,d;
    res=0;
    memset(pr,0,sizeof(pr));//右边的值为0
    for(i=0;i<num;i++)//左边的值为INF
        pl[i]=INF;
    memset(match,-1,sizeof(match));//匈牙利算法
    for(i=0;i<num;i++)
    {
        
        for(j=0;j<num;j++)//辅助数组slack[]初始为无穷大
            slack[j]=INF;
        
        while(1)//死循环知道有满足的匹配为止
        {
            memset(visl,0,sizeof(visl));
            memset(visr,0,sizeof(visr));
            if(dfs(i))break;//匹配成功结束
            d=INF;
            for(j=0;j<num;j++)
                if(!visr[j]&&d>slack[j])//找到一个改进量dx,dx=min{Li+Lj-wi,j}(i∈S,j不∈T)        
                    d=slack[j];    
                //Li=Li-dx (i∈S)
                            for(j=0;j<num;j++)            //Li=Li+dx (i∈T)
                            {                            //重复以上过程,不断的调整L,直到求出完备匹配为止。
                                if(visl[j])
                                    pl[j]-=d;
                                if(visr[j])
                                    pr[j]+=d;
                            }
        }
    }
    for(j=0;j<num;j++)
        res+=g[match[j]][j];
    return res;
}
int main()
{
    int i,j;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(!n&&!m)break;
        
        for(i=0;i<n;i++)
            scanf("%s",map[i]);
        
        int numm,numh;
        numm=numh=0;
        for(i=0;i<n;i++)
        {
            for(j=0;j<m;j++)
            {
                if(map[i][j]=='m')//记录人所在的坐标
                {
                    mm[numm].x=i;mm[numm++].y=j;
                }
                if(map[i][j]=='H')//记录房子所在的坐标
                {
                    hh[numh].x=i;hh[numh++].y=j;
                }
            }
        }
        //建图
        num=numm;
        for(i=0;i<numm;i++)
        {
            for(j=0;j<numm;j++)
            {
                g[i][j]=-(abs(hh[i].x-mm[j].x)+abs(hh[i].y-mm[j].y));//任何人和任何房子间的距离
            }
        }
        /*for(i=0;i<num;i++)
        {
        for(j=0;j<num;j++)
        printf("%d ",g[i][j]);
        printf("\n");
    }*/
        int ans=km();
        printf("%d\n",-ans);
    }
}

 

 1 //hdu2255 竟然700+ms
 2 #include<stdio.h>
 3 #include<string.h>
 4 #define INF 99999999
 5 #define maxn 303
 6 int map[maxn][maxn],match[maxn],visl[maxn],visr[maxn],pr[maxn],pl[maxn],slack[maxn];
 7 int n,m;
 8 int dfs(int u)
 9 {
10     int i,j,val;
11     visl[u]=1;
12     for(i=1;i<=n;i++)
13     {
14         if(!visr[i])
15         {
16             val=pl[u]+pr[i]-map[u][i];
17             if(val==0)
18             {
19                 visr[i]=1;
20                 if(match[i]==-1||dfs(match[i]))
21                 {
22                     match[i]=u;
23                     return 1;
24                 }
25             }
26             if(val>0&&val<slack[i])
27                 slack[i]=val;
28         }
29     }
30     return 0;
31 }
32 int km()
33 {
34     int i,j,res,d;
35     res=0;
36     memset(pr,0,sizeof(pr));
37     for(i=1;i<=n;i++)
38         pl[i]=INF;
39     memset(match,-1,sizeof(match));
40     for(i=1;i<=n;i++)
41     {
42         for(j=1;j<=n;j++)
43             slack[j]=INF;
44         while(1)
45         {
46             memset(visl,0,sizeof(visl));
47             memset(visr,0,sizeof(visr));
48             if(dfs(i))break;
49             d=INF;
50             for(j=1;j<=n;j++)
51             {
52                 if(!visr[j]&&d>slack[j])
53                     d=slack[j];
54             }
55             for(j=1;j<=n;j++)
56             {
57                 if(visr[j])
58                     pr[j]+=d;
59                 if(visl[j])
60                     pl[j]-=d;
61             }
62         }
63     }
64     for(i=1;i<=n;i++)
65         res+=map[match[i]][i];
66     return res;
67 }
68 int main()
69 {
70     int i,j;
71     while(scanf("%d",&n)!=EOF)
72     {
73         for(i=0;i<=n;i++)
74             for(j=0;j<=n;j++)
75             {
76                 if(i==j)map[i][j]=0;
77                 else map[i][j]=INF;
78             }
79         for(i=1;i<=n;i++)
80         {
81             for(j=1;j<=n;j++)
82             {
83                 int z;
84                 scanf("%d",&z);
85                 map[i][j]=z;
86             }
87         }
88         int ans=km();
89         printf("%d\n",ans);
90     }
91 }

 

posted @ 2015-05-26 21:40  sweat123  阅读(427)  评论(0编辑  收藏  举报