【二分图】匈牙利 & KM
【二分图】匈牙利 & KM
二分图
概念:
一个图 \(G=(V,E)\) 是无向图,如果顶点 \(V\) 可以分成两个互不相交地子集 \(X,Y\)
且任意一条边的两个顶点一个在 \(X\) 中,一个在 \(Y\) 中,则称 \(G\) 是二分图
性质:
当且仅当无向图 \(G\) 的所有环都是偶环时, \(G\) 才是个二分图
判定:
可从任意一点开始 \(\text{dfs}\),按距离编号
如果要编号的点已经被编号,可根据奇偶判断是否是二分图
bool dfs(int u) {
for (int i = lst[u], v; i; i = nxt[i]) {
v = to[i];
if (cl[u] == cl[v])
return 0;
if (!cl[v]) {
cl[v] = 3 - cl[u];
if (!dfs(v))
return 0;
}
}
return 1;
}
int main() {
cl[1] = 1;
dfs(1);
}
二分图的匹配
概念:
一个二分图 \(G\) 的一个子图 \(M\), 若在 \(M\) 中的任意两条边都不依附同一个顶点
则称 \(M\) 是一个匹配
最大匹配:
即选择边数最大的匹配
完备匹配:
某一部的顶点全部与匹配中的某条边关联
完美匹配:
所有顶点都和匹配中的某条边关联
增广路
定义:
\(M\) 是二分图 \(G\) 的已匹配边的集合,若 \(P\) 是一条联通两个在不同部的未匹配点的路径,
且路径上匹配边与未匹配边交替出现,则 \(P\) 是相对于 \(M\) 的增广路。
性质:
第一条和最后一条都是未匹配边,边数为奇数
因为要联通两个在不同部的未匹配点
一个增广路径 \(P\) 经过取反可以得到一个更大的匹配 \(M'\)
\(M\) 是 \(G\) 的最大匹配当且仅当不存在相对于 \(M\) 的增广路
最大匹配
匈牙利算法
根据增广路的性质,我们也可以想到一种算法
- 清空 \(M\)
- 寻找 \(M\) 的增广路 \(P\),通过取反得到更大的 \(M'\) 代替 \(M\)
- 重复 (2) 直到找不到增广路为止
这就是匈牙利算法
实现
用 \(\text{dfs}\) ,从 \(X\) 部的一个未匹配点开始,访问邻接点 \(v\) (一定是 \(Y\) 部的)
如果 \(v\) 未匹配,则已找到一条增广路
否则,找出 \(v\) 的匹配顶点 \(w\) (一定是 \(X\) 部的),则 \((w,v)\) 是匹配边
因为要"取反",所以要使 \((w,v)\) 未匹配, \((u,v)\) 匹配。
能实现这一点就需要从 \(w\) 出发找一条新增广路,如果行,则可以找到增广路
实现:
// cx[i] 是 X 部的点 i 匹配的 Y 部点
// cy[i] 是 Y 部的点 i 匹配的 X 部点
bool dfs(int u) {
for (int i = 1; i <= m; i++)
if (mp[u][i] && !vis[i]) {
vis[i] = 1;
if (!cy[i] || dfs(cy[i])) {
cx[u] = i, cy[i] = u;
return 1;
}
}
return 0;
}
inline void match() {
memset(cx, 0, sizeof(cx));
memset(cy, 0, sizeof(cy));
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
}
时间复杂度:
- 邻接矩阵:\(O(n^3)\)
- 前向星:\(O(nm)\)
最大匹配的性质
最小点覆盖 = 最大匹配
最小点覆盖:选择最少的点使得每条边都至少和其中一个点关联
- 证明:最大匹配能保证剩下的边都与至少一个点关联
最小边覆盖 = 总点数 - 最大匹配
最小边覆盖:选择最少的边去覆盖所有点
证明:设总点数是 \(n\),最大匹配是 \(m\)
则最大匹配能覆盖 \(2m\) 个点,设剩下 \(a\) 个点
则这 \(a\) 个点需要单独用 \(a\) 条边覆盖,最小边覆盖 = \(m+a\)
因为 \(2m+a=n\)
所以最小边覆盖 = \((2m+a)-m=n-m\)
最大点独立集 = 总点数 - 最小点覆盖
最大点独立集:在二分图中选出最多的顶点,使得任两点之间没有边相连
证明:删去最小点覆盖的点集,对应的边也没有了,剩下的点就是独立集
因为是最小点覆盖,减去后就是最大点独立集
最佳匹配
如果边有权,则权和最大的匹配叫最佳匹配
有连接 \(X,Y\) 部的顶点 \(X_iY_j\) 的一条边 \(w_{i,j}\) ,要求一种使 \(\sum w_{i,j}\) 最大的匹配
顶标:给顶点的一个标号
设 \(X_i\) 的顶标 \(A_i\) , \(Y_j\) 的顶标 \(B_j\) ,则在任意时刻需要满足 \(A_i+B_j\ge w_{i,j}\) 成立
相等子图:由 \(A_i+B_j=w_{i,j}\) 的边构成的字图
性质:如果相等字图有完备匹配,那么这个完备匹配是最佳匹配
KM
核心:通过修改顶标使得能找到完备匹配
初始化: \(A_i\) 为所有与 \(X_i\) 相连边的最大权, \(B_i=0\)
寻找完备匹配失败,得到一条路径,叫做交错树
将交错树中 \(X\) 部的顶标全部减去 \(d\) , \(Y\) 部的顶标全部加上 \(d\) ,会发现
两端都在交错树中的边, \(A_i+B_j\) 不变,仍在相等字图中
都不在,仍然不在相等字图
\(X\) 不在 \(Y\) 在,\(A_i+B_j\) 增大,仍然不在相等字图
\(X\) 在 \(Y\) 不在,\(A_i+B_j\) 减小,现在可能在相等字图中,使得相等字图扩大
为了使至少有一条边进入相等字图,且 \(A_i+B_j\ge w_{i,j}\) 恒成立
\(d\) 应该等于 \(\min A_i+B_j-w_{i,j}\)
重复 (2) 直到找到完备匹配
复杂度:朴素实现是 \(O(n^4)\) 的
在相等字图上找增广路 \(O(n^2)\) ,每次改顶标最多 \(n\) 次增广路,要改 \(n\) 次顶标
// lx, ly :顶标
// cy[i] 是 y 部点 i 匹配的 X 部点
bool dfs(int x) {
vx[x] = 1;
for (int i = 1, cz; i <= n; i++) {
if (vy[i]) continue;
cz = lx[x] + ly[i] - mp[x][i];
if (cz == 0) {
vy[i] = 1;
if (!cy[i] || dfs(cy[i])) {
cy[i] = x;
return 1;
}
} else lack = min(lack, cz);
}
return 0;
}
inline void KM() {
memset(cy, 0, sizeof(cy));
for (int i = 1; i <= n; i++)
for (;;) {
memset(vx, 0, sizeof(vx));
memset(vy, 0, sizeof(vy));
lack = 2e9;
if (dfs(i)) break;
for (int j = 1; j <= n; j++) {
if (vx[j]) lx[j] -= lack;
if (vy[j]) ly[j] += lack;
}
}
}
\(O(n^3)\) 的做法
其实是可以实现 \(O(n^3)\) 的,
我们给每个 \(Y\) 顶点一个松弛量 \(\text{slack}\) ,初始为正无穷
对于每条边 \((i, j)\), 若不在相等字图中,\(\text{slack}_j=\min(A_i+B_j-w_{i,j})\)
修改顶标时 \(d\) 取所有不在交错树中的 \(Y\) 部点的 \(\text{slack}\) 的最小值
修改完后,所有不在交错树中的 \(Y\) 部点的 \(\text{slack}\) 都减去 \(d\)
int slk[N], pre[N], mat[N];
// mat 等同于 cy
inline void bfs(int st) {
memset(pre, 0, sizeof(pre));
for (int i = 1; i <= n; i++) slk[i] = 1e9;
int x, y = 0, del, pos;
mat[y] = st;
do {
x = mat[y], vy[y] = 1, del = 1e9;
for (int i = 1; i <= n; i++) {
if (vy[i]) continue;
if (slk[i] > lx[x] + ly[i] - mp[x][i])
slk[i] = lx[x] + ly[i] - mp[x][i], pre[i] = y;
if (slk[i] < del) del = slk[i], pos = i;
}
for (int i = 0 ; i <= n; i++) {
if (vy[i]) lx[mat[i]] -= del, ly[i] += del;
else slk[i] -= del;
}
y = pos;
} while (mat[y]);
while (y) mat[y] = mat[pre[y]], y = pre[y];
}
inline void KM() {
memset(mat, 0, sizeof(mat));
memset(lx, 0, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1; i <= n; i++) {
memset(vy, 0, sizeof(vy));
bfs(i);
}
}
答案
for (int i = 1; i <= n; i++) {
if (mp[mat[i]][i]==-1e9 || !mat[i]) {
puts("-1");
break;
}
ans += mp[mat[i]][i];
}
End
【二分图】匈牙利 & KM的更多相关文章
- 训练指南 UVALive - 4043(二分图匹配 + KM算法)
layout: post title: 训练指南 UVALive - 4043(二分图匹配 + KM算法) author: "luowentaoaa" catalog: true ...
- 【算法•日更•第五十期】二分图(km算法)
▎前言 戳开这个链接看看,惊不惊喜,意不意外?传送门. 没想到我的博客竟然被别人据为己有了,还没办法投诉. 这年头写个博客太难了~~~ 之前小编写过了二分图的一些基础知识和匈牙利算法,今天来讲一讲km ...
- 带权二分图最大匹配KM算法
二分图的判定 如果一个图是连通的,可以用如下的染色法判定是否二分图: 我们把X部的结点颜色设为0,Y部的颜色设为1. 从某个未染色的结点u开始,做BFS或者DFS .把u染为0,枚举u的儿子v.如果v ...
- 【算法笔记】二分图与KM算法(当你试图只看蓝书学算法
前言 呜,好久没写博客了,DDL 也有好多,一不留神就轮到我了呜. 看了一眼其它同学写的博客,什么数模啊,什么 CTF 啊,什么 Python 爬虫啊,感觉自己真是越来越菜了呜. 然后在我一愁莫展之际 ...
- hiho1122_二分图匈牙利算法
题目 给定一个图的N个节点和节点之间的M条边,数据保证该图可以构成一个二分图.求该二分图最大匹配. 题目链接:二分图最大匹配 首先通过染色法,将图的N个节点分成两个部分:然后通过匈牙利算法求二 ...
- hdu-1150(二分图+匈牙利算法)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1150 思路:题目中给出两个机器A,B:给出k个任务,每个任务可以由A的x状态或者B的y状态来完成. 完 ...
- [SinGuLaRiTy] 二分图&匈牙利算法
[SinGuLaRiTY-1019] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 二分图 二分图是图论中一种特殊的图形.顾名思义,二分图G ...
- BZOJ1562: [NOI2009]变换序列(二分图 匈牙利)
Description Input Output Sample Input 5 1 1 2 2 1 Sample Output 1 2 4 0 3 HINT 30%的数据中N≤50:60%的数据中N≤ ...
- hdu1853 Cyclic Tour (二分图匹配KM)
Cyclic Tour Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/65535 K (Java/Others)Total ...
随机推荐
- Google kickstart 2022 Round A题解
Speed Typing 题意概述 给出两个字符串I和P,问能否通过删除P中若干个字符得到I?如果能的话,需要删除字符的个数是多少? 数据规模 \[1≤|I|,|P|≤10^5 \] 双指针 设置两个 ...
- Go语言实践模式 - 函数选项模式(Functional Options Pattern)
什么是函数选项模式 大家好,我是小白,有点黑的那个白. 最近遇到一个问题,因为业务需求,需要对接三方平台. 而三方平台提供的一些HTTP(S)接口都有统一的密钥生成规则要求. 为此我们封装了一个独立的 ...
- python基础-基本数据类型(三)
一.散列类型 散列类型用来表示无序的集合类型 1.集合(set) Python中的集合与数学符号中的集合类似,都是用来表示无序不重复元素的集合. 1.1 集合的定义 集合使用一对{}来进行定义,集合中 ...
- 从零开始学YC-Framework之初步
本文主要内容为如下几个方面? YC-Framework的取名出于什么考虑? YC-Framework的特点有哪些? YC-Framework的模块由哪些组成? 为什么要开发YC-Framework? ...
- 详解MySQL索引
原文链接详解MySQL索引 索引介绍 索引是帮助MySQL高效获取数据的数据结构.在数据之外,数据库系统还维护着一个用来查找数据的数据结构,这些数据结构指向着特定的数据,可以实现高级的查找算法. 本文 ...
- 设置Linux系统的交叉编译环境
1.在Linaro官网上获得交叉编译工具 网址:http://releases.linaro.org/components/toolchain/gcc-linaro/ 从Linaro官网上能找到4.9 ...
- Azure Terraform(十一)Azure DevOps Pipeline 内的动态临时变量的使用
思路浅析 在我们分析的 Azure Terraform 系列文中有介绍到关于 Terraform 的状态文件远程存储的问题,我们在 Azure DevOps Pipeline 的 Task Job ...
- [题解] [AGC022E] Median Replace
题目大意 有个奇数长度的 \(01\) 串 \(s\) 其中有若干位置是 \(?\). 每次可将 \(3\) 个连续的字符替换成这三个数的中位数. 求有多少方案将 \(?\) 替换成 \(0/1\) ...
- 没错,华为开始对IoT下手了!
最近,有很多粉丝在后台私信 想知道目前最热的技术是什么? 小编觉得,5G时代到来 物联网技术将迎来快速的发展 加上目前,国内物联网人才短缺 每年人才缺口达百万 IoT物联网将成为最热门的技术 最近,小 ...
- 【原创】ShellCode免杀的骚姿势
ShellCode免杀的骚姿势 常见的免杀手法: shellcode(攻击代码)和加载程序的分离: Lolbins白利用加载shellcode(白名单利用): shellcode混淆.编码解码: sh ...