zoukankan      html  css  js  c++  java
  • 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;
    }

     

  • 相关阅读:
    Vim 使用设置
    stm32之CAN发送、接收详解
    stm32内部的CAN总线
    stm32之CAN总线基础
    JavaScript之Ajax
    JavaScript之insertBefore()和自定义insertAfter()的用法。
    JavaScript之向文档中添加元素和内容的方法
    JavaScript之共享onload
    JavaScrtip之JS最佳实践
    XX秘籍
  • 原文地址:https://www.cnblogs.com/chenhuan001/p/3060449.html
Copyright © 2011-2022 走看看