【学习笔记】二分图
感觉需要恶补一下二分图。
二分图的概念
对于一个无向图,如果其点集能被分为两部分,使得同一部内没有连边,那么这个图就是一个二分图。
二分图有几个基本的性质:
- 一个二分图可以被黑白染色,并且对其进行黑白染色后,同一部的颜色相同、
- 从二分图的某一个点开始 BFS,那么所在的奇数层和偶数层分别为左部和右部的点。
- 一个二分图的每一个环的长度都是偶数。
二分图的匹配
对于一个二分图,若它的边集的一个子集满足每两条边没有公共端点,那么称这个边集为一个匹配。
很多问题都可以转化为二分图的匹配问题,比如变量与值的匹配。
匈牙利算法
匈牙利算法的核心思想就是每一次从左部的一个点开始,寻找一个增广路,满足这条路径上匹配的边和未匹配的边交替出现,最后以未匹配的边结束。这条路的长度为奇数,然后将这条增广路上的边全部翻转,这样每一次就会使得匹配的边集扩大 \(1\),若无增广路则得到了最大匹配。
bool match(int i) {
if (vis[i]) return false;
vis[i] = 1;
for (int j : e[i]) if (!p[j] || match(p[j])) {
p[j] = i;
return true;
}
return false;
}
int hungarian() {
int cnt = 0;
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof vis);
if (match(i)) cnt++;
}
return cnt;
}
一共 DFS \(n\) 遍,于是复杂度为 \(O(nm)\)。
网络流做法
考虑直接建网络流,源点连左部,右部连汇点,直接跑就行。
考虑每次 BFS+DFS 是 \(O(m)\) 的,而对于前 \(\sqrt m\) 轮增广复杂度为 \(O(m \sqrt m)\),而后 \(\sqrt m\) 次增广,增广路径长度大于 \(\sqrt m\),这样的路径不超过 \(\sqrt m\) 条,于是最多只会再进行 \(\sqrt m\) 轮增广,于是复杂度为 \(O(m \sqrt m)\)。
最大权匹配
直接跑费用流吧
upd. 我错了,别跑费用流吧
KM 算法:求最大权完美匹配。如果两边点数不一样就补一下让两边一样。
考虑给每个点赋一个权值 \(l_i\),要求满足 \(w(u, v) \le l_u + l_v\),称为可行顶标。我们记左部点的可行顶标为 \(lx_i\),右部点的可行顶标为 \(ly_i\)。
我们定义相等子图为,将所有满足 \(w(u, v) = lx_u + ly_v\) 的边保留下来得到的图。有定理:若存在某组可行顶标满足相等子图存在完美匹配,那么这组匹配就是最大全完美匹配。证明就放缩一下,可以得到匹配的权值一定小于等于 \(\sum lx_i + ly_i\),若存在完美匹配那么说明这组匹配一定取到了上界。
那么考虑类似于匈牙利的做法,每次考虑找到一个点在相等子图上的匹配,如果能增广成功则结束,否则考虑调整当前的可行顶标使得更多的边加入相等子图,进而存在增广路让匹配增加。考虑交错树(即匈牙利遍历到的所有点形成的树)上的点,记树上的左部点集合为 \(S\),右部点集合为 \(T\),不存在于交错树的集合为 \(S'\) 与 \(T'\)。容易得到,不存在 \(S - T'\) 的边且不存在 \(S'-T\) 的匹配边(否则交错树可以扩大)。
考虑将 \(S\) 中的所有点减 \(d\),\(T\) 中的所有点加 \(d\),那么 \(S-T\) 的边仍然在相等子图内,\(S' - T'\) 内的边不会变,\(S - T'\) 中的 \(lx_u + ly_v\) 减少,可能加入相等子图,\(S' - T\) 中的 \(lx_u + ly_v\) 增加,使得全部从相等子图内移除,由于其本来就不存在匹配边所以不会影响最大匹配。那么我们令 \(d\) 为 \(S - T'\) 的边中 \(lx_u + ly_v - w(u, v)\) 的最小值,这样会使得恰好这一条边加入相等子图。于是我们对 \(T'\) 中的点维护 \(slack(v) = \min \{lx_u + ly_v - w(u, v) | u \in S\}\),那么我们就可以 \(O(n)\) 计算出 \(d = \min\{slack(v) | v \in T'\}\)。
每个点可能会进行 \(O(n)\) 次调整,每次调整后重新匹配是 \(O(n^2)\) 的,于是朴素实现是 \(O(n^4)\) 的。
const long long INF = 0x3f3f3f3f3f3f3f3f;
long long c[MAXN][MAXN], lx[MAXN], ly[MAXN], slk[MAXN];
int match[MAXN];
bool visx[MAXN], visy[MAXN];
bool dfs(int u) {
visx[u] = 1;
for (int v = 1; v <= n; v++) if (!visy[v]) {
long long t = lx[u] + ly[v] - c[u][v];
if (t) {
slk[v] = min(slk[v], t);
} else {
visy[v] = 1;
if (!match[v] || dfs(match[v])) { match[v] = u; return true; }
}
}
return false;
}
long long km() {
for (int i = 1; i <= n; i++) lx[i] = ly[i] = match[i] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) lx[i] = max(lx[i], c[i][j]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) slk[j] = INF;
while (1) {
for (int j = 1; j <= n; j++) visx[j] = visy[j] = 0;
if (dfs(i)) break;
long long d = INF;
for (int j = 1; j <= n; j++) if (!visy[j]) d = min(d, slk[j]);
for (int j = 1; j <= n; j++) if (visx[j]) lx[j] -= d;
for (int j = 1; j <= n; j++) if (visy[j]) ly[j] += d; else slk[j] -= d;
}
}
long long ans = 0;
for (int i = 1; i <= n; i++) ans += c[match[i]][i];
return ans;
}
注意到问题在于每次重新匹配太浪费时间,所以可以复用上一次的匹配结果,从加入的边开始进行增广。额我真看不下去了先咕了,如果真碰到题卡我这个了再回来学吧。
最大匹配相关的转化
最小点覆盖 = 最大匹配
最大独立集 = \(n\) - 最小点覆盖
完美匹配
完美匹配即匹配的数量等于左部点的数量的匹配。
Hall 定理
一个二分图存在完美匹配的充要条件为:对于左部点的任意一个子集,其相连的右部的点集大小都大于等于这个子集的大小。
例子:
\(a_i \in [l_i, r_i]\),并且 \(a_i \not \in S\),求是否存在一组 \(a_i\) 的解,使得 \(a_i\) 互不相同。
将左侧 \(i\) 向右侧 \([l_i, r_i]\) 连边,就变成了是否存在完美匹配的问题。
考虑右部的点的集合,如果为几段不连续的区间,那么我们可以将它划分为几个不相交的子问题。
所以我们只考虑连续的区间,那么只需要满足左侧点的数量小于等于右侧点的数量,于是可以推出 \(\forall L\le R, \sum[L \le l_i \le r_i \le R] \le R - L + 1 - \sum_{i=L}^R[i \in S]\)。
证明:如果 \([L, R]\) 是原来的一个区间,那么这个式子肯定没问题,如果不是原来的一个区间,那么必定会存在一个 \([L', R'] \subseteq [L, R]\) 且 \(\sum[L \le l_i \le r_i \le R] = \sum[L' \le l_i \le r_i \le R']\),这时候已知 \([L', R']\) 对于上式是成立的,那么 \([L, R]\) 也一定成立。
二分图匹配的可行边和必须边
必须边的定义:二分图的任意一个最大匹配都包含的边。
可行边的定义:至少属于一个二分图的最大匹配的边。
首先对二分图跑一遍网络流。
必须边的判定条件:边的流量为 \(1\),并且两个端点在残量网络中不属于同一个 SCC。
可行边的判定条件:边的流量为 \(1\),或两个端点在残量网络中属于同一个 SCC。
这个东西与网络流中最小割的可行边和必须边是等价的。P4126
网格图模型
对网格图进行黑白染色,则图上相邻的两个点必定为一个黑点一个白点。P5038
P5030:这题比较有意思,可以发现能互相攻击到的点必定在同一种颜色的格子上,且横坐标的奇偶性一定不同,于是可以根据奇偶性来划分左部和右部,然后就是二分图最大匹配问题了。