题目描述
给定有向图 。设 是 的一个简单路(顶点不相交)的集合。如果 中每个定点恰好在 的一条路上,则称 是 的一个路径覆盖。 中路径可以从 的任何一个定点开始,长度也是任意的,特别地,可以为 。 的最小路径覆盖是 所含路径条数最少的路径覆盖。设计一个有效算法求一个 GAP (有向无环图) 的最小路径覆盖。
提示:设 ,构造网络 如下:
每条边的容量均为 ,求网络 的 最大流。
输入格式
第一行有 个正整数 和 。 是给定 (有向无环图) 的顶点数, 是 的边数。接下来的 行,每行有两个正整数 ii 和 表示一条有向边 。
输出格式
从第1 行开始,每行输出一条路径。文件的最后一行是最少路径数。
输入
11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11
输出
1 4 7 10 11
2 5 8
3 6 9
3
说明/提示
由@FlierKing提供SPJ
题解
-
一道二分图的题,在网络流上体现为:
最小路径覆盖 = |G| - 最大匹配数 -> 最小路径覆盖=|G|-最大流 -
证明如下:
首先,若最大匹配数为0,则二分图中无边,也就是说有向图G中不存在边,那么
显然:最小路径覆盖=|G|-最大匹配数=|G|-0=|G|。
若此时增加一条匹配边x1–y2,则在有向图|G|中,x、y在同一条路径上,最小路径覆盖数减少一个。
继续增加匹配边,每增加一条,最小路径覆盖数减少一个,则公式:最小路径覆盖=|G|-最大匹配数得证。
-
建模方式:
-
最后的方案可以利用残量网络用并查集维护。
即从1到n枚举,从每个点向外扫一圈,如果有流从这条边经过,并流向y+n,则合并x与y。
然后n^2输出方案即可。
AC-Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
const int inf = 0x3f3f3f3f;
const int maxn = 6000 * 2 + 150 * 2;
int n, m;
struct Edge {
int to;
int next;
int val;
}edge[maxn << 1]; // 双向边,开 2 倍数组
int head[maxn];
int cnt; // 边的数量,从 0 开始编号
int depth[maxn]; // 分层图标记深度
int cur[maxn]; // 当前弧优化,记录当前点 u 循环到了哪一条边
void add(int u, int v, int w) {
edge[cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].val = w;
head[u] = cnt++;
}
// bfs分层图
bool bfs(int s, int t) {
queue<int>q;
memset(depth, 0, sizeof depth);
depth[s] = 1; // 源点深度为 1
cur[s] = head[s];
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].val > 0 && depth[v] == 0) { // 如果残量不为0,且 v 还未分配深度
depth[v] = depth[u] + 1;
cur[v] = head[v]; //------当前弧优化,注意在bfs里,这样做到了“按需赋值”,因为Dinic本来上界就松得一匹, BFS的过程中不连通的点根本就不用再管了...
if (v == t) // -----分层图汇点优化:遇到汇点直接返回------
return true;
q.push(v);
}
}
}
return depth[t]; // 汇点深度为 0:不存在分层图,返回false;
// 非 0 :存在增广路,返回true
}
// dfs寻找增广路
int dfs(int u, int flow, int t) {
if (u == t || flow <= 0) // 到达汇点
return flow;
int rest = flow;
for (int i = cur[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (depth[v] == depth[u] + 1 && edge[i].val != 0) { // 满足分层图、残量>0 两个条件
int k = dfs(v, min(rest, edge[i].val), t); // 向下增广
if (k < 0) // ------无用点优化-----
depth[v] = 0;
rest -= k;
edge[i].val -= k; // 正向边减
edge[i ^ 1].val += k; // 反向边加
if (rest <= 0) //------剩余量优化:在进行增广的时候,如果该节点已经没有流量,直接退出------
break;
}
}
return flow - rest; // flow:推送量,rest:淤积量,flow - rest:接受量/成功传递量
}
int Dinic(int s, int t) {
int ans = 0;
while (bfs(s, t)) {
ans += dfs(s, inf, t);
}
return ans;
}
int f[maxn];
int findx(int x) {
return f[x] == x ? x : f[x] = findx(f[x]);
}
void join(int a, int b) {
int fa = findx(a);
int fb = findx(b);
if (fa != fb) f[fb] = fa;
}
int main() {
ios;
while (cin >> n >> m) {
::cnt = 0;
memset(head, -1, sizeof head);
int s_ = 0;
int t_ = n * 2 + 1;
for (int i = 1; i <= n; i++) {
add(s_, i, 1);
add(i, s_, 0);
}
for (int i = 1 + n; i <= 2 * n; i++) {
add(i, t_, 1);
add(t_, i, 0);
}
for (int i = 1, a, b; i <= m; i++) {
cin >> a >> b;
add(a, b + n, 1);
add(b + n, a, 0);
}
int ans = Dinic(s_, t_);
for (int i = 1; i <= n; ++i) f[i] = i;
for (int i = 1; i <= n; ++i) {
for (int u = head[i]; ~u; u = edge[u].next) {
if (edge[u].to == s_ || edge[u].to == t_) continue;
if (!edge[u].val)
join(i, edge[u].to - n);
}
}
for (int i = n; i; --i) {
bool flag = false;
for (int j = 1; j <= n; ++j) {
if (findx(j) == i) {
cout << j << " ";
flag = true;
}
}
if (flag) cout << endl;
}
cout << n - ans << endl;
}
}