KM算法学习小记:

KM算法用于解决二分图最大权匹配问题,这个问题应该是可以用费用流就解决的。

近期遇到了用KM算法去解不等式的题,虽然转换完后还是可以用费用流做,学习中感觉到顶标挺有用的。

学习自:

https://blog.csdn.net/c20180630/article/details/71080521

https://www.cnblogs.com/huyufeifei/p/10350763.html



假设我们解决的是最大权完全匹配问题,非完全匹配之后再讨论怎么做。

设\(a[i][j]\)为左边第i个点到右边第j个点的最大边的权值,如果没有就是-inf。

一开始,对左边的点,定义定标\(hl[x]\),初值为\(x\)的出边的最大权值,对右边的点,也定义定标\(hr[y]\),初值为0。

当前的相等子图的定义是:只保留\(hl[x]+hr[y]=a[x][y]\)的边。

算法本质上和匈牙利算法类似,需要为每一个点都找到一个匹配点。

所以流程如下:
枚举左半部分的每一个点x,利用匈牙利算法尝试在当前的相等子图中为x找到一个匹配。

若不能,则找到一个最小的权值D,把已遍历的左边的点的hl-=D,已遍历的右边的点的hr-=D,可以发现左边的点一定比右边的多1(因为x没有匹配点),这样总权值-=D,实现了最小的扩张。

这个权值D就是那些不合法的边中的最小的\(hl[x]+hr[y]-a[x][y]\)。

直接这样写复杂度会被卡到\(O(n^4)\)。

http://uoj.ac/problem/80 这题并过不了。

Code(DFS):

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 405;

int nl, nr, m, x, y, z;
int a[N][N];

const int inf = 1e9;

int hl[N], hr[N], vl[N], vr[N], chox[N], choy[N];
int mi;

int find(int x) {
    vl[x] = 1;
    fo(y, 1, nr) {
        if(vr[y]) continue;
        int t = hl[x] + hr[y] - a[x][y];
        if(!t) {
            vr[y] = 1;
            if(!choy[y] || find(choy[y])) {
                chox[x] = y; choy[y] = x;
                return 1;
            }
        } else mi = min(mi, t);
    }
    return 0;
}

int main() {
    scanf("%d %d %d", &nl, &nr, &m);
    fo(i, 1, m) {
        scanf("%d %d %d", &x, &y, &z);
        a[x][y] += z;
    }
    fo(i, 1, nl) fo(j, 1, nr) hl[i] = max(hl[i], a[i][j]);
    int Nr = nr; nr = max(nr, nl);
    fo(i, 1, nl) {
        while(1) {
            memset(vl, 0, sizeof vl);
            memset(vr, 0, sizeof vr);
            mi = inf;
            if(find(i)) break;
            fo(j, 1, nl) if(vl[j]) hl[j] -= mi;
            fo(j, 1, nr) if(vr[j]) hr[j] += mi;
        }
    }
    ll ans = 0;
    fo(i, 1, nl) ans += hl[i];
    fo(i ,1, nr) ans += hr[i];
    pp("%lld\n", ans);
    fo(i, 1, nl) pp("%d ", a[i][chox[i]] ? chox[i] : 0);
}

然后在网上发现还有一种BFS写法,我理解了好久,虽然本质上没有区别。

Code(BFS):

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 405;

int nl, nr, m, x, y, z;
int a[N][N];

const int inf = 1e9;

int hl[N], hr[N], vl[N], vr[N], chox[N], choy[N], sla[N], pre[N];
int mi;

void bfs(int x) {
    memset(sla, 127, sizeof sla);
    memset(pre, 0, sizeof pre);
    memset(vl, 0, sizeof vl);
    memset(vr, 0, sizeof vr);
    int u = 0, nu;
    choy[u] = x;
    do {
        x = choy[u];
        ll D = 1e9;
        vr[u] = 1;
        fo(y, 1, nr) if(!vr[y]) {
            ll t = hl[x] + hr[y] - a[x][y];
            if(t < sla[y]) {
                sla[y] = t;
                pre[y] = u;
            }
            if(sla[y] < D) {
                D = sla[y], nu = y;
            }
        }
        hl[choy[0]] -= D; hr[0] += D;
        fo(i, 1, nr) {
            if(vr[i]) {
                hl[choy[i]] -= D, hr[i] += D;
            } else sla[i] -= D;
        }
        u = nu;
    } while(choy[u]);
    for(; u; u = pre[u])
        choy[u] = choy[pre[u]];
}

int main() {
    scanf("%d %d %d", &nl, &nr, &m);
    fo(i, 1, m) {
        scanf("%d %d %d", &x, &y, &z);
        a[x][y] += z;
    }
    fo(i, 1, nl) fo(j, 1, nr) hl[i] = max(hl[i], a[i][j]);
    nr = max(nr, nl);
    fo(i, 1, nl) bfs(i);
    ll ans = 0;
    fo(i, 1, nl) ans += hl[i];
    fo(i, 1, nr) ans += hr[i];
    fo(i, 1, nr) chox[choy[i]] = i;
    pp("%lld\n", ans);
    fo(i, 1, nl) pp("%d ", a[i][chox[i]] ? chox[i] : 0);
}


问题1:

不完全匹配怎么做?

方法:

\(a[x][y]\)若没有边,则\(a[x][y]=0\),且如果左边点比右边点多,则右边要补一些空点,这样当一个点匹配和它没有边的点时,相当于不选。

问题2:
bfs的写法只能用邻接矩阵存边,就是两两点之间一定要有条边,不然UOJ那题的样例就会挂。

这是因为bfs写法的特殊性,读者可以自行理解其中的奥妙(bfs的顺序出来的不一定是最优的增广路)。

问题3:

开头说到的解不等式,下面这题:

https://ac.nowcoder.com/acm/contest/4010/I

即\(x[i]+y[j]>=a[i][j]>=0\)

求\(\sum x+\sum y\)的最小值。

直接做KM,最后剩下的顶标就是答案,顶标和就是最大权匹配,所以可以直接费用流。

我的提交:

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=42982840

原文地址:https://www.cnblogs.com/coldchair/p/12309736.html

时间: 2024-08-30 13:01:54

KM算法学习小记:的相关文章

BSGS算法学习小记(大步小步算法)

简介 先看一个式子xy≡z(modp),z是质数 现在只知道x和z,要求y. 大步小步算法(BSGS,Baby Steps Giant Steps)就是解决这个问题. 算法流程 暴搜的枚举范围 根据费马小定理:xz?1≡1. 如果y已经枚举到了z-1了,继续枚举的话就会产生循环. 所以,在暴搜中y的枚举范围就是0--z-1. 如何优化暴搜 我们想一想可不可以用分块来解决枚举的y. 把y分成p?1????√分别枚举行不行? 设m=p?1????√,y=a?m+b,这样枚举a和b就相当于分块枚举了.

带修改的莫队算法学习小记

简介 莫涛大神创造出的离线询问算法的带修改版. 算法基础:需要掌握莫队算法,会打暴搜(暴力). 一个叫莫的双端队列. 只支持单点修改 操作方法 普通的不带修改的莫队算法要把每个询问带上两个关键字排序,现在待修改的莫队算法要带上三个关键字排序. 初始操作 fo(i,1,m) { scanf("%s%d%d",s,&k,&l); if (s[0]=='Q')a[++tot].l=k,a[tot].r=l,a[tot].x=num,a[tot].p=tot; else d[+

莫队算法学习小记

算法创始人 莫涛大神. 莫涛队长的算法,%%%%%%%%% 算法简介 算法前提 可以在O(1)的时间内把[l,r]的询问转移到[l-1,r],[l+1,r],[l,r-1],[l,r+1]的询问,而且不需要修改操作,那么就可以使用莫队算法([a,b]表示从a到b的区间,包含a和b) 算法核心 假如有一个询问[l,r]要转移到一个询问[l1,r1],那么需要的时间为O(|l1?l|+|r1?r|),在算法前提下,可以用这么多的时间暴力转移. 但是可以发现有时候有些点会被来回算很多次,这样大量浪费了

Cipolla算法学习小记

转自:http://blog.csdn.net/doyouseeman/article/details/52033204 简介 Cipolla算法是解决二次剩余强有力的工具,一个脑洞大开的算法. 认真看懂了,其实是一个很简单的算法,不过会感觉得出这个算法的数学家十分的机智. 基础数论储备 二次剩余 首先来看一个式子x2≡n(modp),我们现在给出n,要求求得x的值.如果可以求得,n为mod p的二次剩余,其实就是n在mod p意义下开的尽方.Cipolla就是一个用来求得上式的x的一个算法.

堆排序算法学习小记

1.完全二叉树的概念 若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树. 完全二叉树是由满二叉树而引出来的.对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树. (1)所有的叶结点都出现在第k层或k-l层(层次最大的两层) (2)对任一结点,如果其右子树的最大层次为L,则其左子树的最大层次为L或L+l. 一棵二叉树至多只有最下面

ACM学习历程—POJ3565 Ants(最佳匹配KM算法)

Young naturalist Bill studies ants in school. His ants feed on plant-louses that live on apple trees. Each ant colony needs its own apple tree to feed itself. Bill has a map with coordinates of n ant colonies and n apple trees. He knows that ants tra

km算法(二分图最大权匹配)学习

啦啦啦! KM算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转 化为求完备匹配的问题的.设顶点Xi的顶标为A[i],顶点Yi的顶标为B[i],顶点Xi与Yj之间的边权为w[i,j].在算法执行过程中的任一时刻,对于任一条边(i,j), A[i]+B[j]>=w[i,j]始终成立. KM算法的正确性基于以下定理: *  若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配. * 这个定理是显然

linux学习小记 (一 )

shell 学习小记: 注意:多看系统脚本  多模仿    su切换用户时需要输入目标用户密码,root(superuser)切换到任何用户都不需要输入密码,- 参数必须要是最后一个(su huhu -) sudo需要输入当前用户密码,拥有sudo特权的用户可以执行 "sudo su -"命令,使用自己的密码切换到root用户 , 所以应该在/etc/sudoers 文件中禁止 sudo 执行su命令 linux文件与颜色: /etc/DIR_COLORS   (命令dircolors

数据挖掘算法学习(一)K-Means算法

博主最近实习开始接触数据挖掘,将学习笔记分享给大家.目前用的软件是weka,下篇文章会着重讲解. 算法简介: K-Means算法是输入聚类个数k,以及包含n个数据对象的数据库,输出满足方差最小标准的k个聚类.并使得所获得的聚类满足:同一聚类中的对象相似度较高:而不同聚类对象相似度较小. 算法假设: 均方误差是计算群组分散度的最佳参数. 算法输入: 聚类个数k:包含n个数据对象的数据集. 算法输出: k个聚类 算法思想: (a)绿点表示数据集在二级的欧几里德空间,初始化的中心点u1和u2用红的和蓝