hdu 2255(KM)

KM最佳匹配。

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

设 G(V,E) 为二部图, G'(V,E') 为二部图的子图。如果对于 G' 中的任何边<x,y> 满足, L(x)+ L(y)== Wx,y,我们称 G'(V,E') 为 G(V,E) 的等价子图或相等子图(是G的生成子图)。

若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。

因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和(即不是最优匹配)。所以相等子图的完备匹配一定是二分图的最大权匹配。

Important:

1  如果图中|V1|!=|V2|,理解的时候可以补充一些虚拟的点,让两边的点数相等(完备匹配就成为完美匹配了)。当然虚拟点的边之都是0。

2  为什么可以保证相等子图就一定有完备匹配呢?关键就是标号的不断变化可以使得边权为0的边(其实边权为0就不算是边了,但我们可以想像为虚拟的边)也加入到相等子图中,因为完全可能存在A[i]=0,b[j]=0 。最后可以发现凡是原图无法提供的边都是由这类虚拟边来代替的。

最简单的例子就是一个没有边的二分图,最优匹配当然就是0了,其相等子图其实是个完全的二分图,每一个V1的顶点都有一条边权为0的边到达V2的所有顶点,而其相等子图的完备匹配当然就是由一些为0的边组成的,而且所有点的标号也都是0. 所以无论原图中的边够不够,相等子图都可以找出一个完备匹配。

由上面两条分析可以看出来,相等子图的作用其实不在于找完备匹配,而是在在完备匹配的过程中对标号的修改,修改保证完备匹配一定可以找到,而标号本身则是证明km算法正确性的关键。

基于上述两点进行扩充,所有上面的定理也就很好理解了。

 

 

算法流程:

 

  (1)初始化可行顶标的值 

 

     将V1的点的标号记为与其相连边的最大边权值,V2的点标号全记为0

  (2)用匈牙利算法在相等子图寻找完备匹配   
       (3)若未找到完备匹配则修改可行顶标的值 ,扩充相等子图
       (4)重复(2)(3)直到找到相等子图的完备匹配为止

 

 

这个KM算法,想了很久,可能是因为最近感情上的一些问题,所以弄了差不多一个星期才终于弄懂。 下次理解算法的时候一定要仔细看给出的条件。

 

奔小康赚大钱

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1838    Accepted Submission(s): 799


Problem Description
传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子。
这可是一件大事,关系到人民的住房问题啊。村里共有n间房间,刚好有n家老百姓,考虑到每家都要有房住(如果有老百姓没房子住的话,容易引起不安定因素),每家必须分配到一间房子且只能得到一间房子。
另一方面,村长和另外的村领导希望得到最大的效益,这样村里的机构才会有钱.由于老百姓都比较富裕,他们都能对每一间房子在他们的经济范围内出一定的价格,比如有3间房子,一家老百姓可以对第一间出10万,对第2间出2万,对第3间出20万.(当然是在他们的经济范围内).现在这个问题就是村领导怎样分配房子才能使收入最大.(村民即使有钱购买一间房子但不一定能买到,要看村领导分配的).
 

 

Input
输入数据包含多组测试用例,每组数据的第一行输入n,表示房子的数量(也是老百姓家的数量),接下来有n行,每行n个数表示第i个村名对第j间房出的价格(n<=300)。
 

 

Output
请对每组数据输出最大的收入值,每组的输出占一行。

 

 

Sample Input
2 100 10 15 23
 

 

Sample Output
123
 

 

Source
 

 

Recommend
lcy
 

 

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
#define N 303
#define INF 0x3fffffff

int g[N][N];
int pre[N];
int wx[N],wy[N];
int markx[N],marky[N],mark[N]; // 一个是用来记录dfs遍历经过的点,一个是实际能到达的点
int save[N];
int n;

int dfs(int s)
{
    markx[s]=1;
    for(int i=1;i<=n;i++)
    {
        if( wx[s]+wy[i]-g[s][i] < save[i]) save[i]=wx[s]+wy[i]-g[s][i];
        if(mark[i]==1 || wx[s]+wy[i]!=g[s][i]) continue;
            mark[i]=1;
        marky[i]=1;
        if(pre[i]==-1||dfs(pre[i]))
        {
            pre[i]=s;
            return 1;
        }
    }
    return 0;
}

int KM()
{
    memset(pre,-1,sizeof(pre));
    memset(wy,0,sizeof(wy));
    memset(wx,0,sizeof(wx));
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            wx[i]=max(wx[i],g[i][j]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        while(1)
        {
            for(int j=1;j<=n;j++)
                save[j]=INF;
            memset(mark,0,sizeof(mark));
            memset(markx,0,sizeof(markx));
            memset(marky,0,sizeof(marky));
            if(dfs(i)==1) break;
            int mi=INF;
            for(int j=1;j<=n;j++)
                if(marky[j]==0&&save[j]<mi) mi=save[j];
            for(int j=1;j<=n;j++)
                if(markx[j]==1) wx[j]-=mi;
            for(int j=1;j<=n;j++)
                if(marky[j]==1) wy[j]+=mi;
        }
    }
    int sum=0;
    for(int i=1;i<=n;i++)
        sum += g[pre[i]][i];
    return sum;
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&g[i][j]);
        printf("%d\n",KM());
    }
    return 0;
}

 

 

posted @ 2013-05-05 00:12  chenhuan001  阅读(294)  评论(0编辑  收藏  举报