题目链接:传送门
最小路径覆盖问题
最小路径覆盖就是用最少的路径条数,不重复不遗漏地覆盖一个有向无环图的所有顶点。
解法:
- 首先将原图中的每个点拆为和,原图中的点在左侧,拆出来的大于的点在右侧,这样就形成了一个二分图。
- 对于原图中的边,在新建立的二分图中连边,容量为,这样建出来的图叫拆点二分图。
然后,最小路径覆盖 = 原图点数 – 拆点二分图的最大匹配
对这个定理的简单理解:将原图拆成二分图之后,每个点都是独立的一个路径,长度为,每连一条边就会减少一条路径,连边的过程就是匹配的过程,所以要使路径最少,匹配就要最大。
这个最大匹配用匈牙利和网络流都可
但是题目要求输出路径,中标记路径中的起点再记录一下后继即可
#include <bits/stdc++.h>
#define A 1000010
using namespace std;
struct node {int nt, to, w;}e[A];
int hd[A], num = -1;
void add(int fr, int to, int w) {e[++num].nt = hd[fr]; e[num].to = to; e[num].w = w; hd[fr] = num;}
int n, m, dep[A], nxt[A], S, T, a, b, cur[A]; bool vis[A], inq[A];
bool bfs() {
memset(dep, 0x3f, sizeof dep); memset(vis, 0, sizeof vis);
queue<int> q; q.push(S); dep[S] = 0; vis[S] = 1;
while (!q.empty()) {
int fr = q.front(); q.pop(); vis[fr] = 0;
for (int i = hd[fr]; ~i; i = e[i].nt) {
int ca = e[i].to;
if (dep[ca] > dep[fr] + 1 and e[i].w) {
dep[ca] = dep[fr] + 1;
if (!vis[ca]) vis[ca] = 1, q.push(ca);
}
}
}
return dep[T] != 0x3f3f3f3f;
}
int dfs(int fr, int flow, int now = 0) {
if (fr == T) return flow;
for (int &i = cur[fr]; ~i; i = e[i].nt) {
int ca = e[i].to;
if (dep[ca] == dep[fr] + 1 and e[i].w) {
now = dfs(ca, min(flow, e[i].w));
if (now) {
nxt[fr] = ca - n; inq[ca - n] = 1;
e[i].w -= now; e[i ^ 1].w += now;
return now;
}
}
}
return 0;
}
int dinic(int ans = 0) {
while (bfs()) {
memcpy(cur, hd, sizeof hd);
while (int t = dfs(S, 0x3f3f3f3f)) ans += t;
}
return ans;
}
int main(int argc, char *argv[]) {
memset(hd, -1, sizeof hd);
scanf("%d%d", &n, &m); S = 0; T = n << 1 | 1;
for (int i = 1; i <= m; i++) scanf("%d%d", &a, &b), add(a, b + n, 1), add(b + n, a, 0);
for (int i = 1; i <= n; i++) add(S, i, 1), add(i, S, 0), add(i + n, T, 1), add(T, i + n, 0);
int xxw = n - dinic();
for (int i = 1; i <= n; i++)
if (!inq[i]) {
for (int j = i; j; j = nxt[j]) printf("%d ", j);
puts("");
}
printf("%d\n", xxw);
}