• 二分图最优匹配「KM算法」


    KM算法用来求二分图最大权完美匹配
    一般对KM算法的描述,基本上可以概括成以下几个步骤:
    (1) 初始化可行标杆
    (2) 用匈牙利算法寻找完备匹配
    (3) 若未找到完备匹配则修改可行标杆
    (4) 重复(2)(3)直到找到相等子图的完备匹配

    二分图匹配里面我们找最大边进行连边!
    但是遇到某个点被匹配了两次怎么办?
    那就用匈牙利算法进行更改匹配
    这就是KM算法的思路了:尽量找最大的边进行连边,如果不能则换一条较大的。

    引用链接
    添加标杆(顶标)

    是怎样子呢?我们对左边每个点Xi和右边每个点Yi添加标杆Cx和Cy。

    其中我们要满足Cx+Cy>=w[x][y](w[x][y]即为点Xi、Yi之间的边权值)

    对于一开始的初始化,我们对于每个点分别进行如下操作

    Cx=max(w[x][y]);

    Cy=0;

    然后,我们可以进行连边,即采用匈牙利算法,只是在判断两点之间是否有连线的条件下,因为我们要将最大边进行连线,所以原来判断是否有边的条件w[x][y]==0换成了

    Cx+Cy==w[x][y]

    此时,有一个新的名词——相等子图

    因为我们通过了巧妙的处理让计算机自动连接边权最大的边,换句话说,其他边计算机就不会连了,也就“不存在”这个图中,但我们可以随时加上这些“不存在”图中的边。此时这个图可以认为是原图的子图,并且是等效。

    这样,计算机在枚举右边的点的时候,满足以上条件,就能够知道这条边是我们要连的最大的边,就能进行连边了。

    于是乎我们连了AD。

    接下来就尴尬了,计算机接下来要连B点的BD,但是D点已经和A点连了,怎么办呢???

    根据匈牙利算法,我们做的是将A点与其他点进行连线,但此时的子图里“不存在”与A点相连的其他边,怎么办呢??

    为此,我们就需要加上这些边!

    很明显,我们添边,自然要加上不在子图中边权最大的边,也就是和子图里这个边权值差最小的边。

    于是,我们再一度引入了一变量d,d=min{Cx[i]+Cy[j]-w[i][j]}

    其中,在这个题目里Cx[i]指的是A的标杆,Cy[j]是除D点(即已连点)以外的点的标杆。

    随后,对于原先存在于子图的边AD,我们将A的标杆Cx[i]减去d,D的标杆Cy[d]加上d。

    这样,这就保证了原先存在AD边保留在了子图中,并且把不在子图的最大权值的与A点相连的边AE添加到了子图。

    因为计算机判断一条边是否在该子图的条件是其两端的顶点的标杆满足

    Cx+Cy==w[x][y]

    对于原先的边,我们对左端点的标杆减去了d,对右端点的标杆加上了d,所以最终的结果还是不变,仍然是w[x][y]。

    对于我们要添加的边,我们对于左端点减去了d,即Cx[i]=Cx[i]-d;为方便表示我们把更改后的的Cx[i]视为Cz[i],即Cz[i]=Cx[i]-d;

    对于右端点,我们并没有对其进行操作。那这条我们要添加边的两端点的标号是否满足Cz[i]+Cy[j]=w[i][j]?

    因为Cz[i]=Cx[i]-d;d=Cx[i]+Cy[j]-w[i][j];

    我们把d代入左式可得Cz[i]=Cx[i]-(****Cx[i]+Cy[j]-w[i][j]);

    化简得Cz[i]+Cy[j]=w[i][j]。

    ------满足了要求!即添加了新的边。--------

    值得注意的是,这里我们只是对于一条边操作,当我们添加了几条边,要进行如上操作时,要保证原先存在的边不消失,那么我们就要先求出了d,然后

    对于每个连边的左端点(记作集合S)的每个点的标号减去了d之后,然后连边的右端点(记作T)加上d,这样就保证了原先的边不消失啦~

    实际上这就是一直在寻找着增广路,通过不断修改标杆进行添边实现。

    接下来就继续着匈牙利算法,直到完全匹配完为止。

    该算法的正确性就在于 它每次都选择最大的边进行连边

    至此,我们再回顾KM算法的步骤:

    * 1.用邻接矩阵(或其他方法也行啦)来储存图。*

    2.运用贪心算法初始化标杆。

    *3.运用匈牙利算法找到完备匹配。 *

    *4.如果找不到,则通过修改标杆,增加一些边。 *

    5.重复3,4的步骤,直到完全匹配时可结束。

    引用链接
    现在有N男N女,有些男生和女生之间互相有好感,我们将其好感程度定义为好感度,我们希望把他们两两配对,并且最后希望好感度和最大。

    怎么选择最优的配对方法呢?

    首先,每个女生会有一个期望值,就是与她有好感度的男生中最大的好感度。男生呢,期望值为0,就是……只要有一个妹子就可以啦,不挑~~

    这样,我们把每个人的期望值标出来。

    接下来,开始配对。

    配对方法:

    我们从第一个女生开始,分别为每一个女生找对象。

    每次都从第一个男生开始,选择一个男生,使男女两人的期望和要等于两人之间的好感度

    注意:每一轮匹配每个男生只会被尝试匹配一次!

    具体匹配过程:

    为女1找对象=

    (此时无人配对成功)

    根据 “男女两人的期望和要等于两人之间的好感度”的规则

    女1-男1:4+0 != 3

    女1-男3:4+0 == 4

    所以女1选择了男3

    女1找对象成功

    ==为女1找对象成功

    为女2找对象=

    (此时女1—男3)

    根据配对原则,女2选择男3

    男3有主女1,女1尝试换人

    我们尝试让女1去找别人

    尝试失败

    为女2找对象失败!

    ==为女2找对象失败

    这一轮参与匹配的人有:女1,女2,男3。

    怎么办???很容易想到的,这两个女生只能降低一下期望值了,降低多少呢?

    任意一个参与匹配女生能换到任意一个这轮没有被选择过的男生所需要降低的最小值(就可以保证每个女的都可以匹配到一个男的)

    比如:女1选择男1,期望值要降低1。 女2选择男1,期望值要降低1。 女2选择男2,期望值要降低2。

    于是,只要期望值降低1,就有妹子可能选择其他人。所以妹子们的期望值要降低1点。

    同时,刚才被抢的男生此时非常得意,因为有妹子来抢他,于是他的期望值提高了1点(就是同妹子们降低的期望值相同)。

    于是期望值变成这样(当然,不参与刚才匹配过程的人期望值不变)

    ==继续为女2找对象=

    (此时女1—男3)

    女2选择了男1

    男1还没有被配对

    女2找对象成功!

    ==为女2找对象成功=

    为女3找对象=

    (此时女1—男3,女2-男1)

    女3没有可以配对的男生……

    女3找对象失败

    ==为女3找对象失败

    此轮只有女3参与匹配

    此时应该为女3降低期望值

    降低期望值1的时候,女3-男3可以配对,所以女3降低期望值1

    ==继续为女3找对象

    (此时女1—男3, 女2-男1)

    女3相中了男3

    此时男3已经有主女1,于是女1尝试换人

    女1选择男1

    而男1也已经有主女2,女2尝试换人

    前面说过,每一轮匹配每个男生只被匹配一次

    所以女2换人失败

    女3找对象再次失败

    ==为女3找对象失败

    这一轮匹配相关人员:女1,女2,女3,男1,男3

    此时,只要女2降低1点期望值,就能换到男2

    (前面提过 只要任意一个女生能换到任意一个没有被选择过的男生所需要降低的最小值)

    我们把相应人员期望值改变一下

    ==还是为女3找对象

    (此时女1—男3, 女2-男1)

    女3选择了男3

    男3有主女1,女1尝试换人

    女1换到了男1

    男1已经有主女2,女2尝试换人

    女2换人男2

    男2无主,匹配成功!!!

    ==为女3找对象成功=

    匹配成功!!!撒花~~

    到此匹配全部结束

    此时

    女1-男1,女2-男2,女3-男3

    好感度和为最大:9

    #include<bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    int love[310][310];//每个女生对男生的好感度(W两点之间的权值)
    int exboy[310];//男生的吸引值(右标杆)
    int exgirl[310];//女生的期望值(左标杆)
    int visboy[310];//标记是否在此次匹配中出现
    int visgirl[310];//标记是否在此次匹配中出现
    int match[310];//标记每个男生匹配到的女生编号,没有匹配到则为-1
    int slack[310];//记录每个男生想被女生匹配到最小需要多少吸引值
    int n;
    int Hungarian(int girl)
    {
        visgirl[girl]=1;//标记这个女生已经在匹配中出现
        for(int boy=1; boy<=n; boy++)
        {
            if(visboy[boy])//男生已经被匹配,就继续下一个男生
            {
                continue;
            }
            int tep=exboy[boy]+exgirl[girl]-love[girl][boy];
            if(tep==0)//男生的吸引值加上女生的期望值==女生对男生的好感度(左标杆+右标杆==左标杆节点到右标杆节点的权值)
            {
                visboy[boy]=1;
                if(match[boy]==-1||Hungarian(match[boy]))
                {
                    match[boy]=girl;//标记这个男生匹配到的女生编号
                    return true;
                }
            }
            else//意味着男生的吸引值不够,记录每个男生需要匹配到女生的最小吸引值
            {
                slack[boy]=min(slack[boy],tep);
            }
    
        }
        return false;
    }
    void KM( )
    {
        memset(match,-1,sizeof(match));
        memset(exboy,0,sizeof(exboy));//开始每个男生都没有女生与他匹配,吸引值为0
        for(int i=1; i<=n; i++)
        {
            exgirl[i]=love[i][1];
            for(int j=2; j<=n; j++)
            {
                exgirl[i]=max(exgirl[i],love[i][j]);//初始化每个女生的期望值都是最大的(初始化左标杆)
            }
        }
        // cout<<"flag"<<endl;
        // for(int i=1;i<=n;i++)
        // {
        //     cout<<exgirl[i]<<" "<<exboy[i]<<endl;
        // }
        for(int i=1; i<=n; i++)//每一个女生开始进行匹配
        {
            memset(slack,INF,sizeof(slack));//寻找最小的吸引值,所以初始化为正无穷
            while(1)
            {
                memset(visgirl,0,sizeof(visgirl));
                memset(visboy,0,sizeof(visboy));
                if(Hungarian(i))//找到匹配策略,跳出循环
                {
                    break;
                }
    //没有找到匹配策略,于是更新女生的期望值(左标杆),男生的吸引值(右标杆)
                int d=INF;
                for(int j=1; j<=n; j++)
                {
                    if(!visboy[j])
                    {
                        d=min(d,slack[j]);//找出最小的吸引值(那个男生就可以匹配到女生啦)
                    }
                }
                for(int j=1; j<=n; j++)
                {
                    if(visgirl[j])//在匹配中出现的女生为了妥协,期望值下降d(心里不甘心下降,于是尽可能的下降少一点期望值)
                    {
                        exgirl[j]-=d;
                    }
                    if(visboy[j])//在匹配中出现的男生由于有女生抢他,所以吸引值上升d(上升的值完全取决于女生想下降多少,于是就上升了最小的d)
                    {
                        exboy[j]+=d;
                    }
                    else//没在匹配中的男生,由于女生的期望值降低,所以他需要的吸引值也下降d(不用那么辛苦训练吸引值了)
                    {
                        slack[j]-=d;
                    }
                }
            }
        }
        int res=0;
        for(int i=1; i<=n; i++)
        {
            res+=love[match[i]][i];//第i个男生匹配到的女生编号是match[i]
        }
        printf("%d
    ",res);
    }
    int main( )
    {
        while(~scanf("%d",&n))
        {
            for(int i=1; i<=n; i++)
            {
                for(int j=1; j<=n; j++)
                {
                    scanf("%d",&love[i][j]);
                }
            }
            KM( );
        }
        return 0;
    }
    
  • 相关阅读:
    转载 初学者必看——最简单最清晰的Struts2项目搭建流程
    五种常见设计模式
    第二则java读取excel文件代码
    使用Maven运行Java main的3种方式使用Maven运行Java main的3种方式
    C++模式学习------策略模式
    C++模式学习------工厂模式
    人生辣么多的谎言,没必要一个个试一下
    常用函数说明
    python 查看与更换工作目录
    unix常用命令记录
  • 原文地址:https://www.cnblogs.com/lcbwwy/p/13125327.html
Copyright © 2020-2023  润新知