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("\n");
        for(int j=1;j<=n;j++)
            printf("%d ",y[j]),res+=y[j];
        printf("\n%d\n",res);
    }
}
posted @ 2020-05-10 17:56  An_Fly  阅读(189)  评论(0编辑  收藏  举报