二分图学习笔记
预备知识
- 二分图:如果一张无向图\((V,E)\)存在点集\(A,B\),满足\(|A|,|B|≥1\),\(A∩B=\empty\),\(A∪B=V\),且对于\(x,y∈A\)或\(x,y∈B\),\((x,y)\notin E\),则称这张无向图为二分图,\(A,B\)分别为二分图的左部和右部。
- 图的匹配:对于一张无向图\((V,E)\),若存在一个边集\(E'\),满足\(E'\sube E\),且对于任意\(p,q\in E'\),\(p,q\)没有公共端点,则称\(E'\)为这张无向图的一组匹配。
- 匹配边/非匹配边:对于任意一组匹配\(S\),属于\(S\)的边称为匹配边,不属于\(S\)的边称为非匹配边。
- 匹配点/非匹配点:匹配边的端点称为匹配点,其他点称为非匹配点。
- 匹配的增广路:如果二分图中存在一条连接两个非匹配点的路径\(path\),使得非匹配边与匹配边在\(path\)上交替出现,则称\(path\)是匹配\(S\)的增广路。
- 性质1:长度为奇数。
- 性质2:奇数边是非匹配边,偶数边是匹配边。
- 性质3:如果把路径上所有边的状态(是否为匹配边)取反,那么得到的新的边集\(S'\)仍是一组匹配,并且匹配的边数增加了1.
- 最大匹配:在二分图中,包含边数最多的一组匹配。
- 完备匹配:在二分图\(((A,B),E)\)中,设最大匹配为\(E'\),且有\(|A|=|B|=|E'|\),则称二分图有完备匹配。
- 最优匹配:对于一张边有边权的二分图,所有最大匹配中边权总和最大的,称为最优匹配。
二分图判定
判定定理
一个无向图是二分图,当且仅当图中不存在奇环。
核心流程
一般应用染色法即可。
- 将所有节点初始化为未染色。
- 从一个未染色的节点\(u\)开始,染色为\(c\)。(如果\(u\)没有前驱,则\(c\)取\(0\)或\(1\)均可)
- 从\(u\)出发遍历所有与其连接的节点\(v\),如果:
- \(v\)未染色,设\(c\)为节点\(u\)的相反色,跳转至第2步;
- \(v\)颜色与\(u\)相同,则该图不是二分图;
- \(v\)颜色是\(u\)的相反色,继续;
可以看出其实是一个DFS的过程。如果给定图是连通图,则一次DFS即可;否则需要遍历每一个点,每次从未染色的点开始DFS。
模板
bool dfs(int u, int c) {
col[u] = c;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (col[v] == col[u]) return false;
if (col[v] == -1 && !dfs(v, !col[u])) return false;
}
return true;
}
二分图匹配
二分图最大匹配
判定定理
二分图的一组匹配\(S\)是最大匹配,当且仅当图中不存在\(S\)的增广路。
匈牙利算法
- 初始时设匹配\(S\)为空集,即所有边都是非匹配边。
- 枚举二分图左部上的点\(x\),给\(x\)寻找与其相连的右部点\(y\)尝试匹配,当满足下列条件之一时,匹配成功(找到增广路):
- \(y\)为非匹配点
- \(y\)已与\(x'\)匹配,但从\(x'\)出发能找到另一个\(y'\)与之匹配
- 重复第2步,直到找不到增广路。
时间复杂度\(O(p\times e+q)\),其中\(p\)为左部点数量,\(q\)为右部点数量,\(e\)为图的边数。
一个小小的优化:当左部点数量明显大于右部点数量时,改为枚举右部点。
另\(Dinic\)跑最大流的时间复杂度是\(O(n\sqrt e)\),其中\(n\)为节点总数,\(e\)为图的边数。
模板
bool vis[maxn];
int res[maxn];
//vis[i]记录节点i在试图改变匹配对象时成功与否
//res[i]记录节点i的匹配对象
bool match(int x) {
//注意,参数x都是左部点
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].to;
if (!vis[y]) {
//对于左部点x而言,右部点y还没有试图“腾出来”过
vis[y] = true;
//尝试了就只有两种结果:y要么最终配上了x,要么实在腾不出来
//无论是哪一种,y都没必要再次尝试“腾出来”了,所以只试一次就行
if (!res[y] || match(res[y])) {
res[x] = y;
res[y] = x;
//这里默认左部点和右部点的编号没有重复的
return true;
}
//y还没有匹配过,或y的匹配对象x'可以找到新的匹配对象
//则本次x与y的匹配成功
}
}
return false;
}
int main() {
...
int ans = 0;
for (int i = 1; i <= p; i++) {
memset(vis, false, sizeof(vis));
//对于枚举的每一个左部点,右部点的状态都是还没尝试过
if (match(i)) ans++;
}
...
}
二分图最优匹配
对于一张边有边权的二分图,所有最大匹配中边权总和最大的,称为最优匹配。
KM算法
- 对于每一个左部点,以与它相连的所有边中的最大边权,给它赋一个期望值。对于每一个右部点,期望值设为0.
- 枚举每一个左部点,开始匹配。原则:只选择边权与左右部点期望值之和相同的边。
- 如果匹配成功,继续枚举。如果找不到符合要求的未匹配的边,那么尝试让符合要求的已匹配的边的右部点改换匹配对象。这一步与匈牙利算法大致一样,可以看作是寻找增广路\(path\)。
- \(path\)上的所有左部点期望值\(-z\);\(path\)上的所有右部点期望值\(+z\).其中\(z\)为能使左部点找到新边的最小改变量。
- 重复第3步。
注意:在匈牙利算法中,\(vis\)数组是用于记录右部点\(y\)是否已尝试过;而在KM算法中,\(vis\)数组既有前述功能,也标记了节点\(i\)是否在增广路\(path\)上,以便期望值的增减。所以每枚举一个左部点,都要令vis[x]=true
.
模板
int gap;
//记录能使节点配对成功的最小改变量
bool match(int x) {
vis[x] = true;
//别漏了这一步
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].to;
if (!vis[y]) {
int tmp = val[x] + val[y] - e[i].dis;
if (tmp == 0) {
vis[y] = true;
if (!res[y] || match(res[y])) {
res[x] = y;
res[y] = x;
return true;
}
}
else if (tmp > 0) {
gap = min(gap, tmp);
}
}
}
return false;
}
void km() {
for (int i = 1; i <= n; i++) {
while (1) {
gap = INF;
memset(vis, false, sizeof(vis));
if (match(i)) break;
//找不到符合要求的边,降低期望,重新尝试匹配
for (int i = 1; i <= p; i++) {
if (vis[i]) val_x[i] -= gap;
}
//左部点降低期望
for (int i = 1; i <= q; i++) {
if (vis[i]) val_y[i] += gap;
}
//右部点提高期望
}
}
}
二分图最小点权覆盖集
定义
- 图的点覆盖:对于无向图\((V,E)\),若存在一个点集\(V'\sube V\),对于任意\(e\in E\),\(e\)至少有一个端点属于\(V'\),则称\(V'\)为图的一组点覆盖
- 二分图最小点权覆盖:在二分图中,包含点最少/点权最小的一组点覆盖为最小点权覆盖。
定理
最小点覆盖(所包含的点数)\(=\) 最大匹配(包含的边数)
二分图最大独立集
定义
- 图的独立集:对于无向图\((V,E)\),若存在一个点集\(V'\),满足\(V'\sube V\),且对于任意\(p,q\in V'\),边\((p,q)\notin E\),则称\(V'\)为这张图的独立集。
- 图的最大独立集:包含点数最多的独立集。
- 图的团:对于无向图\((V,E)\),若存在一个点集\(V'\),满足\(V'\sube V\),且对于任意\(p,q\in V'\),边\((p,q)\in E\),则称\(V'\)为这张图的一组团。
- 图的最大团:包含点数最多的团。
定理
对于一张\(n\)个节点的二分图,设其最大独立集为\(V'\),最小点覆盖集为\(A'\),最大匹配为\(B'\),有\(|V'|=n-|A'|=n-|B'|\)
DAG最小路径覆盖
定义
- 最小不相交路径覆盖:能覆盖所有节点且互不相交的路径的最少数量。
- 最小可相交路径覆盖:能覆盖所有节点且可以相交的路径的最少数量。
- 拆点二分图:设DAG的节点总数为\(n\),将每个节点拆成编号为\(x\)和\(x+n\)的两个点。建立一张新的二分图,其中编号\(1\)$n$的节点为左部,编号$n+1$\(2n\)的节点为右部。对于原图的每条有向边\((x,y)\),在二分图的左部点\(x\)与右部点\(y+n\)之间连边。最后得到的二分图为原图的拆点二分图。
定理
DAG的最小不相交路径覆盖=原图的节点数 - 新图的最大匹配
DAG的最小可相交路径覆盖的求法:用\(Floyd\)对原图求传递闭包,即可转化为求最小不相交路径覆盖。
模型要素
二分图匹配模型
- 点能分成内部没有边的两个集合
- 每个点只能与1条匹配边相连
二分图最小点覆盖模型
- 每条边有两个端点,二者至少选择一个