浅谈km算法解决二分图最大权匹配问题(附二分图基本定理证明)

时间:2022-06-08 06:13:15

浅谈km算法解决二分图最大权匹配问题(附二分图基本定理证明)

前置知识:

熟练的掌握二分图基本知识,会运用二分图染色和最大匹配。(好像并没什么用

二分图匹配大家都知道吧,二分图最大匹配大家也多知道吧,这里就不过多的进行讲述。其实是我懒得写。

可以看一下我这篇题解:超级英雄

对了推荐一个动态模拟二分图匹配的网址,超级好用:透彻


引子

下面引入这样一个问题:

\(GMP\)琛哥在放假回来后带了一堆零食,立刻被\(ghj1222\),锤子和真硕看到了,他们说,琛哥,我看你这行吗?

琛哥没同意,然后他就把雷哥控制住了(QAQ好像哪里不对(大雾。

锤子,ghj,真硕就开始分琛哥的零食。每个人对不同的零食都有自己的偷税值。

怎么分零食,才能让所有人偷税值最大?

浅谈km算法解决二分图最大权匹配问题(附二分图基本定理证明)

这就涉及到一个问题:二分图最大权匹配。

即:对于一个二分图,最大匹配的方式可能是不唯一的,如果给每一条边付上权值,会存在一个匹配使权值之和最大。


\(part\ one\)

引用某两个巨佬博客里的一句话:

二分图是特殊的网络流,二分图最大匹配可以用Dinic算法解决。二分图最大权匹配相当最小费用最大流可以用FF算法。

我那么菜,学不会网络流,还是学点简单的吧。
下面进入正题

km算法就可以来解决这个问题。不过有一个前提是这个匹配情况是要在完全匹配(就是各个点都能一一对应另一个点)。(如果只是想求最大权值匹配而不要求是完全匹配的话,请把各个不相连的边的权值设置为0。 )(这里我还不太明白)

二分图最大权匹配他也是二分图匹配啊,同时还是一种特殊的网络流,结合他们的思想,我们可以这样想:每次找最大边进行连边,那如果有一个点被比匹配过怎么办,用匈牙利算法来改变匹配。

所以km算法的思路就是:每次找最大的边连边,如果不能就换一条较大的。

下面要解决的一个问题就是:如何找该点对应的权值最大的边?


\(part\ two\)

继续上面的问题:如何找该点对应的权值最大的边?

km算法比较NB的地方来了:设立标杆

对于每个\(x_i,y_i\),设立一个标杆\(C_x,C_y\)

满足\(C_x+C_y>=w_{x,y}\)

  • 初始化

\(C_x=max(C_x,w_{x,y});\)

\(C_y=0;\)

(我还是不知道为什么QAQ???)

  • 连边

用匈牙利算法,判断两点之间能否连线,再将最大边连线。这里把\(w_{x,y}==0\)的条件换成了\(C_x+C_y==w_{x,y}\)

  • (所以链式前向星没法做吧?(大雾)

\(part\ three\)

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

我们现在已经可以让计算机连接最大的边


\(part\ five\)

\(if\)要求边权值最小的匹配呢???

我们可以把边权值取负值,得出结果后再取相反数就可以了。


\(Code\)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int qwq=0x7fffffff;
int w[1000][1000];  //w数组记录边权值 

int line[1000],usex[1000],usey[1000],cx[1000],cy[1000];  //line数组记录右边端点所连的左端点,usex,usey数组记录是否曾访问过,也是判断是否在增广路上,cx,cy数组就是记录点的顶标 

int n,ans,m;  //n左m右 

bool find(int x){
    usex[x]=1;
    for (int i=1;i<=m;i++){
        if ((usey[i]==0)&&(cx[x]+cy[i]==w[x][i])){   //如果这个点未访问过并且它是子图里面的边 
            usey[i]=1;
            if ((line[i]==0)||find(line[i])){   //如果这个点未匹配或者匹配点能更改 
                line[i]=x;
                return true;
            }
        }
    }
    return false;
}

int km(){
    for (int i=1;i<=n;i++){  //分别对左边点依次匹配 
        while (true){
        int d=qwq;
        memset(usex,0,sizeof(usex));
        memset(usey,0,sizeof(usey));
        if (find(i)) break;  //直到成功匹配才换下一个点匹配 
        for (int j=1;j<=n;j++)
            if (usex[j])
             for (int k=1;k<=m;k++)
             if (!usey[k]) d=min(d,cx[j]+cy[k]-w[j][k]);  //计算d值 
        if (d==qwq) return -1;  //不存在完全匹配。//没有用
        for (int j=1;j<=n;j++)
         if (usex[j]) cx[j]-=d;   
        for (int j=1;j<=m;j++)
         if (usey[j]) cy[j]+=d;     //添加新边 
       }
    }
    ans=0;
    for (int i=1;i<=m;i++)
     ans+=w[line[i]][i];
    return ans;
}

int main(){
    while (~scanf("%d%d",&n,&m)){
    memset(cy,0,sizeof(cy));
    memset(w,0,sizeof(w));
    memset(cx,0,sizeof(cx));
    for (int i=1;i<=n;i++){
     int d=0;
     for (int j=1;j<=n;j++){
      scanf("%d",&w[i][j]);
      d=max(d,w[i][j]);   //此处顺便初始化左边点的顶标 
       }
    cx[i]=d;
    }
    memset(line,0,sizeof(line));
    printf("%d\n",km());
}
    return 0;
}

KM算法

习题

\(U53388\)

运动员最佳匹配问题

附(大雾

二分图定理的一些证明:

1.最小顶点覆盖

定义:能覆盖所有的边的最少顶点数(或是最小点权和)

最小顶点覆盖 = 最大匹配数

证明:假设最大匹配是M。为了求最少的点让每条边都至少和期中一个点关联。 M个点是足够的。就是说他们覆盖最大匹配的那M条边后,假设有某边没被覆盖,那么把它加入后会得到一个更大的匹配,出现矛盾。 M个点是必需的。匹配的M条边,由于他们两两无公共点,就是说至少有M个点才能把他们覆盖,所以啊,最小顶点覆盖 = 最大匹配数

2.最大独立点集

定义:在二分图中,选择一些点,使得这些点两两没有边直接相连。

最大独立点集=总点数-最小顶点覆盖

证明:可以这么想,最小顶点覆盖里的每一个点,会去尽可能多的覆盖更多的边,这样一来,这个点覆盖的边的对面的一群点,就全会被算到最大独立点集中,如果最大独立点集中的点有边,那最大匹配数会更优,最小顶点覆盖 也会更大,所以最大独立点集=总点数-最小顶点覆盖

3.DAG最小路径覆盖

定义:能覆盖所有点的最少路径数

最小路径覆盖 = 原图上的点数 - 最大匹配数

证明:先说一下建图

DAG转化成二分图:将一个点拆分成两个点:入点和出点,如果A,B之间有边,就把A的出点和B的入点相连。

RT↓
浅谈km算法解决二分图最大权匹配问题(附二分图基本定理证明)

这张图建完转成二分图是这个样子(且已经匹配完
浅谈km算法解决二分图最大权匹配问题(附二分图基本定理证明)

匹配完了就是这个样子:
浅谈km算法解决二分图最大权匹配问题(附二分图基本定理证明)

三种颜色即代表了三条路径

可以这样想:如果图不连通,最小路径覆盖即为点数,每多一次匹配,会多覆盖一个点,最小路径数-1。又因为每个点只能用一次,所以最小路径覆盖 = 原图上的点数 - 最大匹配数


\(End\)

大概是写的最后一篇博客了吧QAQ。