zoukankan      html  css  js  c++  java
  • UVa11383 二分图的最佳完美匹配

    分析

    这个题即使看不懂看题目的要求应该也知道是KM算法吧。。。
    emm,首先说为什么是Km算法,因为要求每个行和每个列的和最小对吧,就可以给它们一个项标,KM算法的时候项标初始化都是最大的,而根据算法的不断进行,项标之和只会缩小而不会增大,所以最后匹配完成,所有行和列的项标和最小。
    然后详细说一下KM算法吧,我们首先需要知道以下知识。

    • 记 L(x) 表示结点 x 的标记量,如果对于二部图中的任何边<x,y>,都有 L(x)+ L(y) (geq) W(x,y),我们称 L 为二部图的可行顶标。
    • 设 G(V,E) 为二部图, G'(V,E') 为二部图的子图。如果对于 G' 中的任何边<x,y> 满足, L(x)+ L(y)== Wx,y,我们称 G'(V,E') 为 G(V,E) 的等价子图。
    • 设 L 是二部图 G 的可行顶标。若 L的等价子图 GL 有完美匹配 M,则 M 是 G 的最佳匹配。

    前两个是定义,对于第三个,我们可以这样证明。
    由于算法中一直保持顶标的可行性,所以任意一个匹配的权值之和肯定小于等于所有结点的顶标之和,则相等子图中的完备匹配肯定是最优匹配。
    emm,总之记住就好。
    为了方便描述,把主动去匹配的一部分称为X,被匹配的一部分成为Y,初始的时候X部的点项标为连接该点的所有边中权值最大的边权,这样可以保证接下来每条边都有被匹配的可能,下面我们要做的就是不断去找等价子图中的完美匹配M,找不到的时候就更改项标,这时X部的肯定是向小改而不是向大改,因为最开始的时候就置为了最大,这样做的目的是扩大等价子图的范围,来使得更多边进入等价子图,所以Y部的点要跟着缩小,因为你不能说我把X部的改了,Y部的不动,导致原来在等价子图里的边不在了,这样肯定是亏的,所以接下来要考虑的就是修改哪一部分点了。
    下面分几种情况,对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
    <1>i和j都在增广路中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
    <2>i在增广路中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
    <3>j在增广路中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
    <4>i和j都不在增广路中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
    这样,在进行了这些修改操作后,大部分情况的可行性都不会改变,只有第<2>类,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边,也就达到了扩大相等子图的目的。
    这时如果去找最小的d值,就成了(O(n^4)),为了缩减时间复杂度,引进一个slack数组,这时就省去了找d值的一层循环,时间复杂度(O(n^3))

    再来回顾一下流程。
    <1>设定可行标号的初值;
    <2>基于匈牙利算法求解二分图的完备匹配;
    <3>找不到完备匹配时修改可行标号;
    <4>重复<2><3>步骤,一直到找出完备匹配。
    其实主要难理解的就是slack那步,多加思索就行。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=510;
    int slack[N],x[N],y[N],g[N][N];
    int n,match[N],visx[N],visy[N];
    bool dfs(int u){
        visx[u]=1;
        for(int i=1;i<=n;i++){
            if(visy[i])continue;
            int del=x[u]+y[i]-g[u][i];
            if(del==0){
                visy[i]=1;
                if(match[i]==-1||dfs(match[i])){
                    match[i]=u;
                    return 1;
                }
            }else slack[i]=min(slack[i],del);
        }
        return 0;
    }
    void Km(){
        memset(match,-1,sizeof(match));
        for(int i=1;i<=n;i++){
            memset(slack,0x3f,sizeof(slack));
            while(1){
                memset(visx,0,sizeof(visx));
                memset(visy,0,sizeof(visy));
                if(dfs(i))break;
                int Mx=0x3f3f3f3f;
                for(int j=1;j<=n;j++)
                    if(!visy[j])Mx=min(Mx,slack[j]);
                for(int j=1;j<=n;j++)
                    if(visx[j])x[j]-=Mx;
                for(int j=1;j<=n;j++)
                    if(visy[j])y[j]+=Mx;
                    else slack[j]-=Mx;
            }
        }
    }
    int main(){
        while(~scanf("%d",&n)){
            for(int i=1;i<=n;i++){
                x[i]=y[i]=0;
                for(int j=1;j<=n;j++){
                    scanf("%d",&g[i][j]);
                    x[i]=max(g[i][j],x[i]);
                }
            }
            Km();
            int res=0;
            for(int i=1;i<=n;i++)
                printf("%d ",x[i]),res+=x[i];
            printf("
    ");
            for(int j=1;j<=n;j++)
                printf("%d ",y[j]),res+=y[j];
            printf("
    %d
    ",res);
        }
    }
    
  • 相关阅读:
    7、.net core 使用apollo
    6、初识Apollo
    5、supervisor安装使用
    Linux用户和用户组管理
    监督学习中的“生成模型”和“判别模型”
    Ubuntu 14.04 installation & bugs on Alienware-13
    Majority Element问题---Moore's voting算法
    redis之作为缓存的使用(二)
    redis之作为缓存的使用(五)缓存污染
    redis之作为缓存的使用(四)缓存击穿,雪崩,穿透
  • 原文地址:https://www.cnblogs.com/anyixing-fly/p/12862224.html
Copyright © 2011-2022 走看看