问题模型: DAG 最小路径覆盖
转化模型: 最大流
二分图中最小点覆盖 = 最大匹配。在最大匹配中每组匹配选一个点就是最小点覆盖了。
DAG 最小路径覆盖 = 顶点数 - 最小点覆盖(最大匹配)。
我们可以这么理解如果匹配数为 0
,那么显然路径数 = 顶点数。每增加一条匹配边,那么路径覆盖数就减少一个,所以路径数 = 顶点数 - 匹配数。要想使路径数最少,则应最大化匹配数,所以要求二分图的最大匹配。
建图
一个 DAG 里,我们可以把每个点拆成出点和入点。这样就变成了一个二分图。(不存在奇数环的图都可以转换为二分图。 )
对于一个路径覆盖,有如下性质:
- 每个顶点属于且只属于一个路径。
- 路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。
所以我们可以把每个顶点理解成两个顶点,一个是出发 X
,一个是目标 Y
,建立二分图模型。
因为一个点在这题中只能出一次入一次。所以 S
和 T
连得入点和出点都是容量为 1
的边。对于原图中存在的每条边 (i, j)
,在二分图中连接边 (Xi, Yj)
。
注意,此建模方法求最小路径覆盖仅适用于有向无环图,如果有环或是无向图,那么有可能求出的一些环覆盖,而不是路径覆盖。
输出方案
想特地说一下方案怎么输出。
在 e[]
的结构体里多设几个变量,e[].mask
记录这条边是否是反向边,e[].u
和 e[].to
即 u
和 v
。
设 to[e[i].u]
为 第 i
条边连向的点 e[i].to - n
(记得 - n
)。
设 mark[e[i].to - n] = 1
表示不是路径起点。
递归输出即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 6e4 + 5, inf = 0x7fffffff;
struct Edge {
int next, to, c;
int u, mark;
}e[N << 1];
int n, m, s, t;
int cnt = 1;
int head[N], cur[N];
void add(int u, int v) {
e[++ cnt].to = v; e[cnt].c = 1; e[cnt].u = u; e[cnt].mark = 1; e[cnt].next = head[u]; head[u] = cnt;
e[++ cnt].to = u; e[cnt].c = 0; e[cnt].u = v; e[cnt].mark = 0; e[cnt].next = head[v]; head[v] = cnt;
}
int dep[N];
bool bfs(int x) {
queue<int> q;
memset(dep, 0, sizeof(dep));
dep[x] = 1; q.push(x);
while(!q.empty()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!dep[v] && e[i].c) { dep[v] = dep[u] + 1; q.push(v); }
}
}
if (!dep[t]) return 0;
return 1;
}
int dfs(int u, int flow) {
if (u == t) return flow;
for (int &i = cur[u]; i; i = e[i].next) {
int v = e[i].to;
if (dep[v] == dep[u] + 1 && e[i].c) {
int nowflow = dfs(v, min(flow, e[i].c));
if (nowflow > 0) {
e[i].c -= nowflow;
e[i ^ 1].c += nowflow;
return nowflow;
}
}
}
return 0;
}
int Dinic() {
int res = 0;
while(bfs(s)) {
for (int i = s; i <= t; i ++) cur[i] = head[i];
while (int d = dfs(s, inf)) res += d;
}
return res;
}
int main() {
int to[N], mark[N];
scanf("%d%d", &n, &m);
s = 0, t = 2 * n + 1;
for (int i = 1; i <= n; i ++) {
add(s, i);
add(i + n, t);
}
for (int i = 1; i <= m; i ++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v + n);
}
int ans = n - Dinic();
for (int i = 2; i <= cnt; i ++)
if (e[i].mark == 1 && !e[i].c)
to[e[i].u] = e[i].to - n, mark[e[i].to - n] = 1;
for (int i = 1; i <= n; i ++) {
if (mark[i]) continue;
int k = i;
while (k) {
printf("%d ", k);
k = to[k];
}
printf("\n");
}
printf("%d\n", ans);
return 0;
}