二分图匹配
二分图最大匹配
匈牙利算法
模板:
using size_t = std::uint32_t;
struct edge_without_weight {
size_t u, v;
edge_without_weight() : u(-1), v(-1) {}
edge_without_weight(size_t x, size_t y) : u(x), v(y) {}
};
template <typename iter_t,
typename = std::enable_if_t<std::is_same<typename std::iterator_traits<iter_t>::value_type, edge_without_weight>::value> >
vector<edge_without_weight> bipartite_graph_max_match_km(size_t left, size_t right, iter_t edge_bg, iter_t edge_ed){// 返回匹配边
vector<size_t> match(right, -1);
vector<vector<size_t> > G(left);
for (; edge_bg != edge_ed; ++edge_bg)
G[edge_bg->u].emplace_back(edge_bg->v);
vector<size_t> vis(left, -1);
auto find = [&](auto &&self, size_t left_point, size_t vis_time) -> bool {
if (vis[left_point] == vis_time)return false;
vis[left_point] = vis_time;
for (size_t right_point : G[left_point])
if (match[right_point] == -1 || self(self, match[right_point], vis_time)){
match[right_point] = left_point;
return true;
}
return false;
};
for (size_t i = 0; i < left; ++i)find(find, i, i);
vector<edge_without_weight> ret;
for (size_t i = 0; i < right; ++i)
if (match[i] != -1)ret.emplace_back(match[i], i);
return ret;
}
时间复杂度 \(O(ne+m)\),其中 \(n\) 为左部点数量,\(m\) 为右部点数量,\(e\) 为边数
网络流解法
源点向左部点连边,右部点向汇点连边,最大流即为答案
时间复杂度 \(O(m\sqrt n)\)
DAG 最小路径点覆盖
模板题:P2764 最小路径覆盖问题
选出最少的路径,覆盖 \(\text{DAG}\) 所有点
将每个点 \(i\) 拆为 \(i\) 和 \(i+n\) 两个点,\(1\sim n\) 为左部点,\(n+1\sim 2n\) 为右部点
对于原图的边 \(u\to v\),在新二分图上连 \(u\to v+n\)(得到的图称为原图的拆点二分图)
可证 \(\text{DAG}\) 最小路径点覆盖等于 \(n\) 减去其拆点二分图的最大匹配
模板:
namespace flow_algo {
template <typename iter_t,
typename = std::enable_if_t<std::is_same<typename std::iterator_traits<iter_t>::value_type, edge_without_weight>::value> >
vector<vector<size_t> > DAG_path_cover(size_t n, iter_t edge_bg, iter_t edge_ed){
network net;
size_t source = n << 1, sink = source + 1;
net.set_vertex(sink + 1, source, sink);
for (; edge_bg != edge_ed; ++edge_bg)
net.add_edge(edge_bg->u, edge_bg->v + n);
size_t mid = net.edges.size();
for (size_t i = 0; i < n; ++i){
net.add_edge(source, i);
net.add_edge(i + n, sink);
}
vector<vector<size_t> > ret;
ret.reserve(n - net.DINIC_max_flow());
vector<vector<size_t> > G(n);
vector<size_t> indegree(n);
for (size_t i = 0; i < mid; i += 2)
if (net.edges[i ^ 1].flow)
G[net.edges[i ^ 1].to].emplace_back(net.edges[i].to - n), ++indegree[net.edges[i].to - n];
for (size_t i = 0; i < n; ++i)
if (!indegree[i]){
ret.emplace_back();
size_t u = i;
ret.back().emplace_back(u);
while (!G[u].empty())
ret.back().emplace_back(u = G[u][0]);
}
return ret;
}
}
时间复杂度 \(O((n+m)\sqrt n)\)
DAG 最小路径可重点覆盖
对于原图中直接或间接可达的 \(u\to v\),在新图中间连接 \(u\to v\)(即传递闭包),则新图的 \(\text{DAG}\) 最小路径覆盖等于原图的最小路径可重点覆盖
时间复杂度 \(O(nm+n^2\sqrt n)\)
二分图最小点覆盖
选择原图顶点的一个子集,满足对于任意一条边,其两端点中至少一个在被选择的点集中,则最小的合法子集就是原图最小点覆盖
根据 \(\text{K\"onig}\) 定理,二分图 最小点覆盖 等于其 最大匹配
构造:从每一个非匹配点出发,只能正向穿过非匹配边(包含匹配边的反向边),标记所有可以到达的点,则左部点中没有标记的点和右部点中标记的点是原图的一组最小点覆盖
二分图最大独立集
二分图最大独立集大小 等于 总点数 减去 最小点覆盖,等于 总点数 减去 最大匹配
构造:最小点覆盖的补集即为一种最大独立集
二分图最小边覆盖
选择原图边的一个子集,满足所有顶点都存在至少一条邻边为被选择的边,则最小的合法子集就是原图最小边覆盖
二分图最小边覆盖 等于 总点数 减去 最大匹配
构造:取所有匹配边,并对于每个非匹配点任选一条邻边,则取出的边为原图的一组最小边覆盖
二分图最大匹配必经边
求出残量网络的 \(\text{SCC}\),则两端点不在同一 \(\text{SCC}\) 中的匹配边为 必经边
二分图最大匹配必经点
残量网络上,从源点出发走流量等于 \(1\) 的边所能到达的左部点 和 从汇点出发走流量等于 \(0\) 的边所能到达的右部点 都是 非必经点,除此之外为 必经点
二分图最大匹配可行边
求出残量网络的 \(\text{SCC}\),则 当前匹配中的边 和 两端点在同一 \(\text{SCC}\) 中的边 都是 可行边
二分图最大匹配可行点
至少一条出边为可行边的点为 可行点
参考
- \(\text{2024.12.19 Bipartite Graph.pdf \; \;by lanos212}\)
- 【HAOI2017】新型城市化,二分图最大匹配的必经与可行点边
- 【算法】二分图最大匹配中的 可行/必经 点/边

浙公网安备 33010602011771号