「网络流 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)
// 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 longconst 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;
}