網路流24題 P2764 最小路徑覆蓋問題
阿新 • • 發佈:2020-08-23
思路
問題模型:有向無環圖最小路徑覆蓋
轉化模型:網路最大流
最小路徑覆蓋 = 點的總數 - 網路最大流。
思路大多來自課件,採用了拆點思路,如下方式建圖:
對於原圖中的每個點 \(x\) ,建兩個點 \(x\) 和 \(x′\) 。
對於原圖中的每一條邊 \((i, j)\) ,從 \(i\) 向 \(j′\) 連一條邊。
對這個圖跑網路流求二分圖匹配,求最大流期間記錄路徑,如果連出去的點不是起點,就當做這一條路的下一個點
來一張比較直觀的建圖之後的圖(感謝洛谷,侵刪):
對於每個點 \(i\):
\(i\) 只會匹配一次,則只會有一條形如 \((i, j)\) 的邊被選擇;
\(i′\)
故選擇的一定是若干條不相交簡單路徑。
如果一個點 \(i\) 沒有匹配,則其必為某條路徑的結尾。
所以最後的答案就是 \(n-\) 匹配數。
資料不是很強,\(\text{Dinic}\) 不加優化也能過
程式碼
#include <cmath> #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int A = 1e5 + 11; const int B = 1e3 + 11; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; inline int read() { char c = getchar(); int x = 0, f = 1; for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1; for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48); return x * f; } int n, m, s, t, cnt = 1, ans; int dep[A], head[A], inq[A], vis[A], net[A]; struct node { int to, nxt, val; } e[A]; inline void add(int from, int to, int val) { e[++cnt].to = to; e[cnt].val = val; e[cnt].nxt = head[from]; head[from] = cnt; } inline bool bfs() { queue <int> Q; memset(dep, inf, sizeof(dep)); Q.push(s), dep[s] = 0, inq[s] = 1; while (!Q.empty()) { int x = Q.front(); Q.pop(), inq[x] = 0; for (int i = head[x]; i; i = e[i].nxt) { int to = e[i].to; if (dep[to] > dep[x] + 1 && e[i].val) { dep[to] = dep[x] + 1; if (!inq[to]) inq[to] = 1, Q.push(to); } } } if (dep[t] != inf) return 1; return 0; } int dfs(int x, int flow) { int tmp = 0; if (x == t) return flow; for (int i = head[x]; i; i = e[i].nxt) { int to = e[i].to; if (dep[to] == dep[x] + 1 && e[i].val) { if (tmp = dfs(to, min(flow, e[i].val))) { e[i].val -= tmp, e[i ^ 1].val += tmp; net[x] = to; //記錄路徑 if (x) vis[to - n] = 1; //to一定是右邊的點,且被走了,不是出發點 return tmp; } } } return 0; } int main() { n = read(), m = read(), s = 2 * n + 1, t = 2 * n + 2; for (int i = 1; i <= n; i++) add(s, i, 1), add(i, s, 0); for (int i = n + 1; i <= 2 * n; i++) add(i, t, 1), add(t, i, 0); for (int i = 1; i <= m; i++) { int x = read(), y = read(); add(x, y + n, 1), add(y + n, x, 0); } int now = 0, ans = 0; while (bfs()) while (now = dfs(s, inf)) ans += now; for (int i = 1; i <= n; i++) { if (!vis[i]) { //是一條路的出發點 int cur = i; cout << cur << " "; while (net[cur] != t && net[cur] != 0) cout << net[cur] - n << ' ', cur = net[cur] - n; //不減 n 會出錯,net一定是右方點 puts(""); } } cout << n - ans << '\n'; return 0; }