KM算法讲解

  对于二分图,我们可以用匈牙利来求出来最大匹配,但是如果给定每条边一个权值,我们要求这张图的最大匹配最大(小)权,单纯的用匈牙利就没法解决了,当然用费用流也可以做,但是代码较长,在处理完全二分图的时候时间也较长。

  我们这时引入一个新的算法,就是KM。

  对于KM算法,我们引入顶标概念,规定每个点都有顶标,且左面的点(二分图的左右)的顶标设成X[I],右面的设成Y[I],w[i,j]代表i-->j这条边的权值(我们这里

求最大权值和),那么满足对于任意边,x[i]+y[j]>=w[i,j],那么,我们最后肯定可以找到一种匹配方式(完备匹配),使得这种方式中的每一条边都满足x[i]+y[j]=w[i,j]那么这种匹配方式一定是该二分图的最大权值匹配,因为x[i]+y[j]>=w[i,j],我们取到了w[i,j]的上限,所以是最大权值的。

  那么我们给x[i],y[j]设初值的时候,因为要满足对于所有边x[i]+y[j]>=w[i,j],所以我们将x[i]设成max(w[i,k])将y[i]设成0,这样就满足了。

  那么我们也需要一个点一个点的匹配,类似于匈牙利,就是对于满足x[i]+y[j]=w[i,j]的边才算一组匹配,这样我们就得到了一组可行解。

function match(i:longint):boolean;

var

    j                       :longint;

begin

    fx[i]:=true;

    for j:=1 to m do

        if (w[i,j]=tx[i]+ty[j]) and (not fy[j]) then

        begin

            fy[j]:=true;

            if (link[j]=0) or (match(link[j])) then

            begin

                link[j]:=i;

                exit(true);

            end;

        end;

    exit(false);

end;

  当前的可行解可以构成一张图,包含2*m个点和m个边(交错树),但是当前的可行解不一定是最终的解,可能最后答案的一些边不在当前集合中,这时我们需要更新顶标,使得更多的点可以来更新答案。

  那么我们设一个c,使当前所有在交错树中的X[i]减去c,使y[i]加上c,那么我们可以得到一些性质:

1.两端都在交错树中的边(i,j),X[i]+Y[j]的值没有变化。也就是说,它原来属于交错树,现在仍属于交错树。
2.两端都不在交错树中的边(i,j),X[i]和Y[j]都没有变化。也就是说,它原来属于(或不属于),现在仍属于(或不属于)交错树。
3.X端不在交错树中,Y端在交错树中的边(i,j),它的X[i]+Y[j]的值有所增大。它原来不属于交错树,现在仍不属于交错树。
4.X端在交错树中,Y端不在交错树中的边(i,j),它的X[i]+Y[j]的值有所减小。也就说,它原来不属于交错树,现在可能进入了交错树,因而可以更新了答案。
那么我们要使至少一条边进入答案,这时我们的c的取值就是min(x[i]+y[j]-w[i,j]),i在交错树中,j不在交错树中,所以我们得到了更新顶标的过程
procedure update;

var

    d                       :longint;

    i, j                    :longint;

    c                       :longint;

begin

    c:=maxlongint;

    for i:=1 to n do if fx[i] then

        for j:=1 to m do if not fy[j] then c:=min(c,tx[i]+ty[j]-w[i,j]);



    for i:=1 to n do if fx[i] then dec(tx[i],c);

    for i:=1 to m do if fy[i] then inc(ty[i],c);

end;


那么这样一来到最后我们就可以得到这张图的最大权值匹配了,最小只需取相反数即可

 

附主程序

procedure main;

var

    i, j                    :longint;

begin

    for i:=1 to n do

        for j:=1 to m do

            tx[i]:=max(tx[i],w[i,j]);



    for i:=1 to n do

        while true do

        begin

            fillchar(fx,sizeof(fx),false);

            fillchar(fy,sizeof(fy),false);

            if match(i) then break else update;

        end;



    for j:=1 to m do ans:=ans+w[link[j],j];

    writeln(ans);

end;

 

你可能感兴趣的:(算法)