題目鏈接:傳送門
最小路徑覆蓋問題
最小路徑覆蓋就是用最少的路徑條數,不重複不遺漏地覆蓋一個有向無環圖的所有頂點。
解法:
- 首先將原圖中的每個點拆爲和,原圖中的點在左側,拆出來的大於的點在右側,這樣就形成了一個二分圖。
- 對於原圖中的邊,在新建立的二分圖中連邊,容量爲,這樣建出來的圖叫拆點二分圖。
然後,最小路徑覆蓋 = 原圖點數 – 拆點二分圖的最大匹配
對這個定理的簡單理解:將原圖拆成二分圖之後,每個點都是獨立的一個路徑,長度爲,每連一條邊就會減少一條路徑,連邊的過程就是匹配的過程,所以要使路徑最少,匹配就要最大。
這個最大匹配用匈牙利和網絡流都可
但是題目要求輸出路徑,中標記路徑中的起點再記錄一下後繼即可
#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);
}