二分图学习笔记

预备知识

  • 二分图:如果一张无向图\((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'|\),则称二分图有完备匹配。
  • 最优匹配:对于一张边有边权的二分图,所有最大匹配中边权总和最大的,称为最优匹配

二分图判定

判定定理

一个无向图是二分图,当且仅当图中不存在奇环。

核心流程

一般应用染色法即可。

  1. 将所有节点初始化为未染色。
  2. 从一个未染色的节点\(u\)开始,染色为\(c\)。(如果\(u\)没有前驱,则\(c\)\(0\)\(1\)均可)
  3. \(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\)的增广路。

匈牙利算法

  1. 初始时设匹配\(S\)为空集,即所有边都是非匹配边。
  2. 枚举二分图左部上的点\(x\),给\(x\)寻找与其相连的右部点\(y\)尝试匹配,当满足下列条件之一时,匹配成功(找到增广路):
    • \(y\)为非匹配点
    • \(y\)已与\(x'\)匹配,但从\(x'\)出发能找到另一个\(y'\)与之匹配
  3. 重复第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算法

  1. 对于每一个左部点,以与它相连的所有边中的最大边权,给它赋一个期望值。对于每一个右部点,期望值设为0.
  2. 枚举每一个左部点,开始匹配。原则:只选择边权与左右部点期望值之和相同的边。
  3. 如果匹配成功,继续枚举。如果找不到符合要求的未匹配的边,那么尝试让符合要求的已匹配的边的右部点改换匹配对象。这一步与匈牙利算法大致一样,可以看作是寻找增广路\(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条匹配边相连

二分图最小点覆盖模型

  • 每条边有两个端点,二者至少选择一个
posted @ 2020-10-07 18:23  StreamAzure  阅读(1422)  评论(0编辑  收藏  举报