700C

WIDA

2023-09-28 21:31:39

Solution

## 700C($\tt *2600$;图论-缩点、网络流-最小割) > 一些废话:讲道理我本不该写这篇题解,但是看了一眼这道题的题解区感觉大家讲的都比较杂,百度了一下发现也是如此 ~~可能佬们都觉得这题太基础了所以不愿意多讲~~ 所以我作为一只菜鸟,来综合整理一下。 ![image.png](https://s2.loli.net/2023/09/28/y5icJ7og4duIWKZ.png) 需要分类讨论: - 不需要删边的情况可以很简单的维护搞定(即为判断 $s$ 和 $t$ 是否位于同一个连通块,方法很多,略); - 删去一条边的情况即为删去“有效”割边,这里的“有效”指的是位于 $s$ 到 $t$ 的路径上的割边(例如样例一中 $4-5$ 这一条边就不是“有效”的);我们可以使用 Tarjan 找出全部割边,再使用 dfs 维护有效路径,综合两者即可搞定这一情况; - 删去两条边的情况有两种做法,其一是暴力枚举,其二是使用网络流求解最小割,随后在残余网络寻找答案,需要较高技巧。下方我将分别讲解。 ### 暴力枚举 容易发现最短路径不经过超过 $N$ 个点,所以我们依次枚举 $s$ 到 $t$ 最短路径上的每一条边,然后建立一张不包含这一条边的图,重复上述“删去一条边”的操作。 分析复杂度,我们至多将建立 $N$ 张不同的图,单次查找割边的复杂度为 $\mathcal O(N+M)$ ,所以可以在 $\mathcal O(NM)$ 级别的复杂度内完成。 需要注意的是,非最短路径理论上也可做,但是我的写法超时了,所以改成了最短路径。这一做法细节较多,需要谨慎书写。 ### 优雅网络流 ~~如果你没有学习过网络流,那么可以无视这一做法。~~ 考虑最小割模型。 首先,对于此前求解出的全部割边,我们显然不能将它们割掉,所以它们的边权置为 $\infty$;对于其他边,由于需要解决“删去**两条边**”最小割,所以我们将全部边的边权加上一个不小于总边权之和的基数 $B$,如此一来,求出的答案在 $[2B,3B)$ 这一范围内时即等价于“删去两条边”的最小割(这一步很巧妙)。 随后我们遍历残留网络获取答案。根据最大流的原理,如果某条边是合法的答案,那么这条边之前的残余流量一定非零,这条边和这条边之后的残余流量一定为零,所以我们只需要使用 dfs 沿着有残余流量的边一路搜索,很容易即可找到目标边。 ### 代码环节 首先声明,我并不是 OI 出身,所以码风和正常 OI 不太一样,如果你熟悉 CodeForces 风格那会比较适应;同时我还是个封装怪和 STL 喜爱者,本题中 Tarjan 和网络流的算法细节都依赖于结构体封装实现,数据结构都依赖于 STL,请各位大佬谨慎参考! 对了,如果要在本地进行测试,您可能需要使用 C++17 或更高版本的编译器~ 暴力枚举 ```cpp #include <bits/stdc++.h> using namespace std; struct E_DCC { int n; vector<int> h, ver, ne; vector<int> dfn, low, col, S; int now, cnt, tot; vector<bool> bridge; // 记录是否是割边 E_DCC(int n, int m) : n(n) { m *= 2; // 注意链式前向星边的数量翻倍 ver.resize(m + 1); ne.resize(m + 1); bridge.resize(m + 1); h.resize(n + 1, -1); dfn.resize(n + 1); low.resize(n + 1); col.resize(n + 1); S.clear(); tot = cnt = now = 0; } void add(int x, int y) { // 注意,这里的编号从 0 开始 ver[tot] = y, ne[tot] = h[x], h[x] = tot++; ver[tot] = x, ne[tot] = h[y], h[y] = tot++; } void tarjan(int x, int fa) { // 这里是缩边双,不是缩点,不相同 dfn[x] = low[x] = ++now; S.push_back(x); for (int i = h[x]; ~i; i = ne[i]) { int y = ver[i]; if (!dfn[y]) { tarjan(y, i); // 这里储存的是父亲边的编号 low[x] = min(low[x], low[y]); // y 不能到达 x 的任何一个祖先节点,(x - y) 即为一条割边 // 但是在这里,我们不直接储存 (x - y) 这条边,而是储存边的编号 // 这样做是为了处理重边的情况(点可能相同,但是边的编号绝对不相同) if (dfn[x] < low[y]) { bridge[i] = bridge[i ^ 1] = true; } } else if (i != (fa ^ 1)) { // 这里同样的,使用边的编号来处理重边情况 low[x] = min(low[x], dfn[y]); } } if (dfn[x] == low[x]) { int pre = 0; cnt++; do { pre = S.back(); S.pop_back(); col[pre] = cnt; } while (pre != x); } } set<array<int, 2>> work() { // [新图的顶点数量, 新图] for (int i = 1; i <= n; i++) { // 避免图不连通 if (dfn[i] == 0) { tarjan(i, -1); } } set<array<int, 2>> ans; for (int i = 0; i < tot; ++i) { if (bridge[i]) { // 如果 (i, i ^ 1) 是割边 ans.insert({ver[i], ver[i ^ 1]}); } } return ans; } }; signed main() { int n, m, s, t; cin >> n >> m >> s >> t; E_DCC scc(n, m); vector<vector<array<int, 3>>> ver(n + 1); vector<array<int, 4>> edge; for (int i = 1; i <= m; i++) { int x, y, w; cin >> x >> y >> w; scc.add(x, y); ver[x].push_back({y, w, i}); ver[y].push_back({x, w, i}); edge.push_back({x, y, w, i}); } vector<int> vis(n + 1), type(n + 1); type[t] = 1; auto zero = [&](auto self, int x, int fa) -> void { vis[x] = 1; for (auto [y, w, id] : ver[x]) { if (y == fa) continue; type[x] |= type[y]; if (vis[y]) continue; self(self, y, x); type[x] |= type[y]; } }; zero(zero, s, 0); if (!vis[t]) { // 特判不连通 cout << 0 << '\n'; cout << 0 << '\n'; return 0; } int ans = 2E9 + 7; vector<int> Id; auto chk = scc.work(); for (auto [x, y, w, id] : edge) { if (chk.count({x, y}) && w < ans && type[x] && type[y]) { ans = w; Id = {id}; } } queue<int> q; q.push(s); vector<int> fa(n + 1); map<array<int, 2>, array<int, 2>> dic; fa[s] = -1; while (q.size()) { int x = q.front(); q.pop(); for (auto [y, w, id] : ver[x]) { if (fa[y]) continue; fa[y] = x; dic[{x, y}] = {id, w}; q.push(y); } } vector<array<int, 2>> path; int now = t; while (true) { path.push_back(dic[{fa[now], now}]); now = fa[now]; if (now == s) break; } for (auto [id_, w_] : path) { E_DCC graph(n, m); for (auto [x, y, w, id] : edge) { if (id == id_) continue; graph.add(x, y); } vis.assign(n + 1, 0); type.assign(n + 1, 0); type[t] = 1; auto dfs = [&](auto self, int x, int fa) -> void { vis[x] = 1; for (auto [y, w, id] : ver[x]) { if (y == fa || id == id_) continue; type[x] |= type[y]; if (vis[y]) continue; self(self, y, x); type[x] |= type[y]; } }; dfs(dfs, s, 0); auto chk = graph.work(); for (auto [x, y, w, id] : edge) { if (id != id_ && chk.count({x, y}) && w + w_ < ans && type[x] && type[y]) { ans = w + w_; Id = {id, id_}; } } } if (ans == 2E9 + 7) { cout << -1 << '\n'; } else { cout << ans << '\n'; cout << Id.size() << '\n'; for (auto it : Id) { cout << it << ' '; } } } int __OI_INIT__ = []() { ios::sync_with_stdio(0), cin.tie(0); cout.tie(0); cout << fixed << setprecision(12); return 0; }(); ``` 网络流 ```cpp #define int long long template <typename T> struct Flow_ { const int n; const T inf = numeric_limits<T>::max(); struct Edge { int to; T w; Edge(int to, T w) : to(to), w(w) {} }; vector<Edge> ver; vector<vector<int>> h; vector<int> cur, d; Flow_(int n) : n(n + 1), h(n + 1) {} void add(int u, int v, T c) { h[u].push_back(ver.size()); ver.emplace_back(v, c); h[v].push_back(ver.size()); ver.emplace_back(u, 0); } bool bfs(int s, int t) { d.assign(n, -1); d[s] = 0; queue<int> q; q.push(s); while (!q.empty()) { auto x = q.front(); q.pop(); for (auto it : h[x]) { auto [y, w] = ver[it]; if (w && d[y] == -1) { d[y] = d[x] + 1; if (y == t) return true; q.push(y); } } } return false; } T dfs(int u, int t, T f) { if (u == t) return f; auto r = f; for (int &i = cur[u]; i < h[u].size(); i++) { auto j = h[u][i]; auto &[v, c] = ver[j]; auto &[u, rc] = ver[j ^ 1]; if (c && d[v] == d[u] + 1) { auto a = dfs(v, t, std::min(r, c)); c -= a; rc += a; r -= a; if (!r) return f; } } return f - r; } T work(int s, int t) { T ans = 0; while (bfs(s, t)) { cur.assign(n, 0); ans += dfs(s, t, inf); } return ans; } vector<int> get(int s) { vector<int> vis(n + 1); auto dfs = [&](auto self, int x) -> void { vis[x] = 1; for (auto it : h[x]) { auto [y, w] = ver[it]; if (!vis[y] && w) { self(self, y); } } }; dfs(dfs, s); return vis; } }; using Flow = Flow_<int>; signed main() { int n, m, s, t; cin >> n >> m >> s >> t; E_DCC scc(n, m); vector<vector<array<int, 3>>> ver(n + 1); vector<array<int, 4>> edge; for (int i = 1; i <= m; i++) { int x, y, w; cin >> x >> y >> w; scc.add(x, y); ver[x].push_back({y, w, i}); ver[y].push_back({x, w, i}); edge.push_back({x, y, w, i}); } vector<int> vis(n + 1), type(n + 1); type[t] = 1; auto zero = [&](auto self, int x, int fa) -> void { vis[x] = 1; for (auto [y, w, id] : ver[x]) { if (y == fa) continue; type[x] |= type[y]; if (vis[y]) continue; self(self, y, x); type[x] |= type[y]; } }; zero(zero, s, 0); if (!vis[t]) { // 特判不连通 cout << 0 << '\n'; cout << 0 << '\n'; return 0; } Flow flow(n); int ans = 1E18; vector<int> Id; auto chk = scc.work(); for (auto [x, y, w, id] : edge) { if (chk.count({x, y}) && w < ans && type[x] && type[y]) { ans = w; Id = {id}; } if (chk.count({x, y})) { flow.add(x, y, 1E18); flow.add(y, x, 1E18); } else { flow.add(x, y, w + 1E14); flow.add(y, x, w + 1E14); } } int f = flow.work(s, t) - 2E14; if (0 <= f && f < 1E14 && ans > f) { ans = f; Id.clear(); auto flag = flow.get(s); for (auto [x, y, w, id] : edge) { if (flag[x] ^ flag[y]) { Id.push_back(id); } } } if (ans == 1E18) { cout << -1 << '\n'; } else { cout << ans << '\n'; cout << Id.size() << '\n'; for (auto it : Id) { cout << it << ' '; } } } int __OI_INIT__ = []() { ios::sync_with_stdio(0), cin.tie(0); cout.tie(0); cout << fixed << setprecision(12); return 0; }(); ```