关于KM算法及其延伸的一些解题研究

本文深入探讨KM算法,一种高效解决带权二分图匹配问题的算法,详细讲解其核心思想与实现步骤,并通过具体案例展示算法的应用场景。

关于KM算法及其延伸的一些解题研究

KM算法作为一种简洁明了的算法,巧妙地解决了带权二分图在特定条件(带权最大匹配一定是完备匹配的图)下的求解问题,规避了网络流费用流算法较为繁琐的代码,在信息学竞赛具有相当的地位。为增加本文的可读性,笔者已经将输入输出和初始化的代码隐去。

题目描述

仓库管理员M最近一直很烦恼,因为他的上司给了他一个艰难的任务:让他尽快想出一种合理的方案,把公司的仓库整理好。已知公司共有n个仓库和n种货物,由于公司进货时没能很好的归好类,使得大部分的仓库里面同时装有多种货物,这就给搬运工作人员搬运货物时带来了很多的麻烦。仓库管理员M的任务就是设计一种合理的方案,把仓库里面的货物重新整理,把相同的货物放到同一个仓库,以便于日后的管理,在整理过程中肯定需要把某些货物从一个仓库搬运到另一个仓库,已知每一次搬运货物所付出的代价等于搬运该货物的重量。

编程任务:

请你帮助仓库管理员M设计搬运方案,使得把所有的货物归好类:使每种货物各自占用一个仓库,或者说每个仓库里只能放一种货物。同时要求搬运货物时所付出的所有的总的代价最小。

输入格式

第一行为n (1 <= n <= 150),仓库的数量。

以下为仓库货物的情况。第i+1行依次为第i个仓库中n种货物的数量x(0 <= x <= 100)。

输出格式

把所有的货物按要求整理好所需的总的最小代价。

输入输出样例

输入 

4

62 41 86 94

73 58 11 12

69 93 89 88

81 40 69 13

输出 

650

说明/提示

样例说明:方案是:第1种货物放到仓库2中;第2种货物放到仓库3中;第3种货物放到仓库4中;第4种货物放到仓库1中

首先给出笔者解题代码。定义等略去。

bool dfs(int x)

{

    va[x]=1;

    for(int i=1;i<=n;i++)

    {

        if(!vb[i])

        {

            if((la[x]+lb[i])==w[x][i])

            {

                vb[i]=1;

                if(!match[i]||dfs(match[i]))

                {

                    match[i]=x;

                    return true;

                }

            }

            else delta=min(delta,la[x]+lb[i]-w[x][i]);

        }

    }

    return false;

}

int KM()

{

    for(int i=1;i<=n;i++)

    {

        la[i]=-(1<<30);

        lb[i]=0;

        for(int j=1;j<=n;j++)

        {

            la[i]=max(la[i],w[i][j]);

        }

    }

    for(int i=1;i<=n;i++)

    {

        while(true)

        {

            memset(va,0,sizeof(va));

            memset(vb,0,sizeof(vb));

            delta=(1<<30);

            if(dfs(i))break;

            for(int j=1;j<=n;j++)

            {

                if(va[j])la[j]-=delta;

                if(vb[j])lb[j]+=delta;

            }

        }

    }

    int ans=0;

    for(int i=1;i<=n;i++)

    {

        ans-=w[match[i]][i];

    }

    return ans;

}

int main()

{

    n=read();

    for(int i=1;i<=n;i++)

        for(int j=1;j<=n;j++)

        {

            store[i][j]=read();

            ll[j]+=store[i][j];

        }

    for(int i=1;i<=n;i++)

        for(int j=1;j<=n;j++)

        {

            w[i][j]=-(ll[j]-store[i][j]);

        }   

    cout<<KM();

    return 0;

}

本题几乎可称为KM算法的标准模板,为了进一步介绍及讨论首先引入交错树的概念(默认读者已掌握匈牙利算法的操作方式以及相关术语)

其实也很好理解,就是在一般的匈牙利算法中,从一个左部点出发尝试进行匹配,如果失败,那他所有访问过的节点以及为了访问这些节点所经过的边共同构成交错树。(该树根节点为左部点,叶子也是左部点)

之后是顶标,这个概念为相等子图而服务。 所谓相等子图,就是二分图中所有节点满足Ai+Bi=W(i,j)的边所构成的子图。 而顶标,就是这里的Ai,Bi。实际操作时,将左部点的顶标赋值为Ai,右部点赋值为Bi。

之后我们就可以引入定理

“若相等子图中存在完备匹配,则这个完备匹配就是二分图的带权最大匹配。”

根据以上定理我们可以推知,我们要做的就是采取一个适当的策略扩大相等子图的规模,直到它存在完备匹配。

for(int i=1;i<=n;i++)

    {

        la[i]=-(1<<30);

        lb[i]=0;

        for(int j=1;j<=n;j++)

        {

            la[i]=max(la[i],w[i][j]);

        }

    }

在这里我们将所有的Ai赋值为其所连边中权值最大者,Bi赋值为0。此时他必然满足满足Ai+Bj>=W(i,j)。(事实上,也存在其他赋值方法,本质上来讲,只需满足对于任意i,j有Ai+Bj>=W(I,j)即可,这里采用的是较为简便的赋值方法)

for(int i=1;i<=n;i++)

    {

        while(true)

        {

            memset(va,0,sizeof(va));

            memset(vb,0,sizeof(vb));

            delta=(1<<30);

            if(dfs(i))break;

            for(int j=1;j<=n;j++)

            {

                if(va[j])la[j]-=delta;

                if(vb[j])lb[j]+=delta;

            }

        }

    }

我们对每一个左部节点进行匹配(使用while(true)不达目的不罢休)如果dfs(i)==1,即这个节点可以匹配,那就不管他。否则修改顶标(失败的交错树中的顶标)。 这里有一点需要注意,即修改顶标后,再一次进行dfs时,原来并非相等子图中的边,现在就有可能成为相等子图中的边。而且由于减去的delta最终求得结果为“即将加入的边还比要求多多少”故可保证每次均有>=1条边进入相等子图,从概率的角度来讲,进一步提高了左部点完成匹配的可能。

以上便是该算法的核心思路,重复上述过程直到达成条件即可(所有左部点均匹配成功)。

结语:KM算法在处理最大带权二分图的特殊情况时有一定的优越性,当同样也具有较大的局限性,掌握KM算法后,一定不能满足于只解决特定情况下的问题,也还要积极探索更为高级的算法,解决更为普适性的问题。事实上,理解了二分图惯用的匈牙利算法和KM算法,对费用流的理解也会有所帮助。

感谢洛谷平台提供的题面及测试数据。

 

 

用Matlab生成一个模型,其中O表示领海基线,对应坐标为O1(166.1, 167.7) O2(154.4, 127.7) O3(72.5, 111.5) O4(87.7, 194.2) O5(136.0, 188.4) ,将其各点连线;P表示领海线对应坐标为P1(180.1, 172.6) P2(163.9, 117.3) P3(57.5,96.3) P4(77.9,207.4) P5(140.3, 200.0) 将其各点连线 ; Q表示任务区线对应坐标为Q1(194.0, 177.6)Q2(173.4, 106.9) Q3(42.5, 81.2) Q4(68.1, 220.7) Q5(144.6, 211.5) 将其各点连线。目前我方任务小艇由 J1、J2、J3、J4组成,J1、J2、J3、J4的最高航速(节)分别为20、24 、26、30;小艇点位(海里)分别为:J1(187, 175)J2(169, 112)J3(50, 88) J4(73, 214) ,小艇巡航航线为任务区线和领海线的中线(标记为 A、B、C、D、 E五点连线)。针对的敌方任务对象共有5类小艇,分别为S1、S2、S3、S4,S5,其中S1、S4,S5处于静止状态点位(海里)分别为:S1 (53, 119) S4 (120, 110) S5 (80,190);S2、S3起始点位(海里)分别为:S2 (207, 143) S3 (35, 100),S2航速15节,S3航速15节;S2航向309度,S3航向86.08度;S1现场处置时间为2h ,S4的现场处置时间均2h,S5的现场处置时间为4h,S2、S3仅需驱离,无需计算现场处置用时。 现我方任务小艇J1、J2、J3、J4接到下达的任务,须前往现场对S1、S2、S3、S4,S5;五艘敌方小艇进行处置。 • 假设可行方案:让J1负责S2任务(因为航速高,且处置半径大,适合跟监) • 让J2负责S3任务(同样航速较高,处置半径3海里,适合跟监1海里?注意处置半径3海里完全可以满足1海里的距离要求) • 让J3负责S1任务(因为S1在J3初始位置附近?初始位置:J3(50,88),S1(53,119)距离很近) • 让J4负责S5、S4(J4航速最快且在S5附近) 约束条件: ① 其中 S1、S2、S3必须优先派我方小艇进行处置,且其中 S1、S2、S3任务一旦开始执行不能中途中止。对于S1,任务需要处置的时刻是任务的发生时刻; 对于应对S2,任务需要处置的时刻是指S2实际达到任务区线的时刻; 对于S3,任务需要处置的时刻是指S3达到任务区线的时刻;对于S4,任务需要处置的时刻是任务发生时刻,对于S5,任务需要处置的时刻是任务发生时刻。 ② 我方小艇的活动范围只能位于任务区线内。 ③ 针对敌方S1的现场处置时间为2h,后续派我方小艇将S1护送至O5位置,护送时我方小艇以最大航速航行。 ④ 针对敌方S2应派出我方小艇以最快方式向其机动并对其开展跟监,跟监保持在约3海里距离。S2侵入任务区线后,我方小艇须对其保持全程跟监,直至其驶出我任务区线外,即可认为任务完成,从S2侵入任务区线至S2按轨迹驶出我任务区线为任务完成时间。 ⑤ 针对敌方S3应派出我方小艇以最快方式向其机动并对其开展跟监驱离(应保持在1海里距离内),直至其驶出我任务区线外,即可认为任务完成。S3侵入任务区线后,我方小艇须对其保持全程跟监,直至其驶出我任务区线外,即可认为任务完成,从S3侵入任务区线至S3按轨迹驶出我任务区线为任务完成时间。 ⑥派出的我方J4小艇处置S4、S5时,到相应最大处置半径后需将其驱逐出任务区线,路径为(S4到Q2-Q3的最短距离连线, S5到Q4-Q3的最短距离连线),先处置完S5后需直接从驱离点以最快航速向S4出发。 ⑦注意只要敌方小艇在我方小艇的最大处置半径内即可进行处置(J1、J2、J3、J4的最大处置半径(海里)分别为5、3、1、1,);即无论是驱离还是跟监,我方小艇无需至任务区线,只需保证地方小艇在最大处置半径内即可。 ⑧以T0时作为任务开始的时刻。 ⑨航向的定义:指小艇艇首的朝向,以坐标正北为 0°,顺时针为正,取值范围为 0-360。 请根据上述说明建立相关数学模型,用Matlab生成一个模型示意图,表达出各点位位置(代号)和限制区域,标记出以及我方小艇行进路径和敌方小艇的运动轨迹(小艇的运动轨迹用虚线表示,用不同颜色区分),标注出S2、S3在任务区内运动的轨迹及我方小艇处置的运动轨迹和接触点、送离点(这里需要注意跟监只需保持最大处置半径即可);请构建数学模型,规划怎样分配小艇前出执行任务才能保证任务完成最快(任务完成时间以最后一艘小艇处置到位的时间为准)。 表达出解题思路和计算过程,最后需要得出的结果有:
最新发布
10-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值