「网络流 24 题」最小路径覆盖
思路
具体可以看 这篇博客
对于有向无环图 ,我们只需要将假装 将点裂成左点和右点(实际没有裂开),然后连边;
在上面跑二分图最大匹配 后,剩下没有匹配的左点就是终点 (因为它没有匹配,没有出边了),没有匹配的右点就是起点(因为没有点能够到达它)
那么最小路径覆盖数就是: 点数 − 最大匹配数 点数 - 最大匹配数 点数−最大匹配数
对于输出路径划分方案,我们可以利用 m a t c h match match 数组,因为 u = m a t c h [ v ] u = match[v] u=match[v] 记录的就是 u → v u \rarr v u→v 这条边,并且是最小路径覆盖,那么 v v v 的唯一入度就是 u u u,所以我们可以线性处理
最终从每条路径的起点搜索输出即可
时间复杂度: O ( n m ) O(nm) O(nm)
cpp
// Problem: #6002. 「网络流 24 题」最小路径覆盖
// Contest: LibreOJ
// URL: https://loj.ac/p/6002
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
const int N = 250;
std::vector<int> match; //match[i]表示每个右点i当前匹配的左点
std::vector<bool> used; //当前轮 右点 i 是否被预定
std::vector<int> g[N];
bool dfs(int l){ //为当前左点 l 寻找匹配
for(auto r : g[l])
if(!used[r]){ //如果当前轮右点r还没有被预订
used[r] = true; //预定
if(!match[r] || dfs(match[r])){
match[r] = l;
return true;
//(1)如果右点 r 还没有配对
//(2)右点 r 已经配对,尝试更换其原配左点
}
}
return false;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n, m;
std::cin >> n >> m;
match.assign(n + 1, 0);
while(m--){
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
}
std::vector<int> nxt(n + 1, 0);
int cnt = 0;
fore(i, 1, n + 1){
used.assign(n + 1, false);
cnt += dfs(i);
}
std::vector<bool> head(n + 1, true); //是否为起点
fore(v, 1, n + 1){
int u = match[v];
if(!u) continue;
nxt[u] = v;
head[v] = false;
}
fore(i, 1, n + 1){
if(!head[i]) continue;
int u = i; //从起点开始搜,路径唯一
while(nxt[u]){
std::cout << u << ' ';
u = nxt[u];
}
std::cout << u << endl;
}
std::cout << n - cnt; //最小路径覆盖数量
return 0;
}