hdu 2255奔小康赚大钱 KM算法模板

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255

一,KM算法:(借助这个题写一下个人对km的理解与km模板)

KM算法主要是用来求解图的最优匹配的。

1,带权二分图:  在二分图中每一条边(x,y)对应一个权值Wi这种二分图叫带权二分图。

一个匹配的权值就是该匹配中所有边的权值之和。

2,最优匹配:

权值最大的一个完美匹配,叫做最优匹配。

      《km算法思想》

对于一个带权完全二分图:G(V,E),对于其中每一条边(x,y)边都对应一个权值W(x,y)。

由于是二分图所以节点集合可以分解成两个集合(X,Y)。X={x1,x2,,,,,xn},Y={y1,y2,,,,yn}。

可行性标签:我们将在所有顶点上定义一个实函数F,且对所有定点满足:F(x)+ F(y)>=W(x,y),

则我们称F为图G的一个可行性标签。(这里可以理解成F是对顶点加权值F(x))

平凡标签:对于每一个图都有存在这样一个标签 l:

称该标签为平凡标签

相等子图:对于每一个标签都对应原图的一个子图:G’=(V’,E’)  ,其中V’=V,E’属于E,

且E’中所有边满足:F(x) + F(y)=W(x,y) ,该子图称为可行性标签的F的相等子图。


定理:(KM最重要的定理)

对于一个可行性标签L,其相等子图G’,若存在完美匹配M,则该匹配必是原图G的完美匹配,

因为G中与G’中的节点是完全一样的,且对于该匹配的权值为:


所以对于最大的可行性标签L,若它的相等子图G‘含有完美匹配M,则M就是原图的最优匹配。

注意:上面的平凡匹配就是所有匹配中最大的匹配。

匈牙利树:

匈牙利树,其实是一颗DFS搜索树。但是在搜索是有条件限制。

(1),该树必须要从未匹配点开始搜索,即,该树必须以未匹配点为根。

(2),而且在搜索过程中只能走叫错路。

(3),叶子节点必须是匹配点

注意:若有叶子节点为未匹配点则从树根到该节点一定有一条增广路。

其实在匈牙利算法中,若我们从一个未匹配的点,利用DFS找增广路,若到最后我们没找到增广路,

则我们DFS搜索过程的路线就是一颗DFS树且是一颗匈牙利树,且在匈牙利算法中树我们可以永久

性的地把匈牙利树从图中删去,而不影响结果,即:这颗树对树以外的节点的匹配没有影响。


在上图中(2)不是匈牙利树因为有一个叶子节点7,很明显有一条2到7的增广路。

(3)是一颗匈牙利树。

更新可行性标签:

当我们在当前大的可行性标签l下,无法在L相等子图中找到完美匹配,

这时我们要更新可行性标签,

其实就是在原来的可行性标签l下减少最小的一个值(key)得到另一个可行性标签L。

更新方程为:


       《KM算法》

假设二分图为:G=(V,E),(X,Y)= V。X={x1,x2,,,,,xn},Y={y1,y2,,,,yn}。

1,初始化可行性标签L为平凡标签(最大标签)。

2,在可行性标签相等子图G’=(V,E’)中用匈牙利算法(DFS)对X集合中节点一个个进行匹配。

若在匹配过程中,若有X集合中的点没匹配成功,转向(3)

(3)匹配失败这时DFS索搜出的路径必定是一颗匈牙利树。

由匈牙利树性质可知匈牙利树中的节点与边与匈牙利树外部是相互独立的,所以我们改变匈牙利

树种节点的可行新标签即可,且不会对其它树外有影响,于是我们利用上面标签转化公式改变

匈牙利中节点的可行性标签,然后再转向(2)。

Km 例题:

令:图节点为:V1={x1,x2,x3,x4,x5}  ,V2={y1,y2,y3,y4,y5}。边矩阵为:


初始化可行性标签(平凡标签)

L(y1)= L(y2) = L(y3) = L(y4) = L(y5) = 0,

L(x1)=max(3,5,5,4,1)=5 , L(x2)=max(2,2,0,2,2) =2  ,L(x3)=max(2,4,4,1,0) =4

L(x4)=max(0,1,1,0,0) =1  ,L(x4)=max(0,1,1,0,0) =1 。

L的相等子图为:


L相等子图的匹配(匹配到x4点失败)


x4搜索出的匈牙利树为:


则 S={x4,x1,x3},T={y2,y3} 利用可行性标签变换公式为L’:

可得到:key=1;改变:

L’(x1)=4,L’(x2)= 2, L’(x4)=0 ;   L’(y2)=1,L’(y3)=1。


然后x4继续匹配:可以匹配完:

这上图L’完美匹配也就是原图最优匹配。


W(M)=12。

代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
const int Max=310;
const int Inf=(1<<31)-1;
int N;
int graph[Max][Max];//图
int match[Max];//行匹配
int l_x[Max];//x可行性标签
int l_y[Max];//y可行性标签
int usedx[Max];//标记数组
int usedy[Max];

bool Hungary(int x)
{//匈牙利DFS
    usedx[x]=true;
    for(int i=0;i<N;i++)
    {
        if(!usedy[i] && l_x[x]+l_y[i]==graph[x][i])
        {
            usedy[i]=true;
            if(match[i]==-1 || Hungary(match[i]))
            {
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}


void KM()
{
    memset(l_x,0,sizeof(l_x));
    memset(l_y,0,sizeof(l_y));
    for(int i=0; i<N; i++)
    {//初始化可信性标签
        for(int j=0; j<N; j++)
        {
            if(l_x[i]<graph[i][j])
                l_x[i]=graph[i][j];
        }
    }
    for(int i=0;i<N;i++)
    {//匹配V1所以点
        while(1)
        {
            memset(usedx,false,sizeof(usedx));
            memset(usedy,false,sizeof(usedy));
            if(Hungary(i))
                break;//匹配成功就跳出
            else
            {//匹配失败 匈牙利树节点更新
                int tem=Inf;//求最小的 key
                for(int j=0;j<N;j++)
                {//用到那公式
                    if(usedx[j])
                    {
                        for(int k=0;k<N;k++)
                        {
                            if(!usedy[k] && l_x[j]+l_y[k]-graph[j][k]<tem)
                                 tem= l_x[j]+l_y[k]-graph[j][k];
                        }
                    }
                }
                for(int j=0;j<N;j++)
                {//更新匈牙利树可行性标签
                    if(usedx[j])
                        l_x[j]-=tem;
                    if(usedy[j])
                        l_y[j]+=tem;
                }
            }
        }
    }
}

int main()
{
    while(scanf("%d",&N)!=EOF)
    {
        for(int i=0; i<N; i++)
            for(int j=0; j<N; j++)
                scanf("%d",&graph[i][j]);
        memset(match,-1,sizeof(match));
       KM();
       int total=0;
       for(int i=0;i<N;i++)
       {
           total+=l_x[i];
           total+=l_y[i];
       }
       cout<<total<<endl;
    }
    return 0;
}















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值