From cd7dd748138e4944f1d5839bafaec9d7cf41d43d Mon Sep 17 00:00:00 2001 From: manoflearning <77jwk0724@gmail.com> Date: Mon, 29 Dec 2025 20:11:55 +0900 Subject: [PATCH 1/5] docs: change AGENTS.md --- AGENTS.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 02904a6..f474e6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,14 +11,16 @@ When adding or editing code here, optimize for contest usage first. 5. **Template-friendly**: easy to reuse across problems with minimal edits. ## Repo standard code style (required) + ### Naming -- **Struct names**: `PascalCase` (capitalized). - - Example: `struct FenwickTree { ... };` - **Constants** (e.g., `constexpr`, global `const`, fixed parameters): `UPPER_SNAKE_CASE`. - Examples: `constexpr int MOD = 1e9 + 7;`, `const int INF = 1e9;` -- **Everything else** uses **lowercase `snake_case`**: - - namespaces, functions, variables, file names - - Examples: `namespace fast_io`, `add_edge`, `max_flow`, `solve_case`, `pref_sum` +- **All other identifiers** use **lowercase `snake_case`** (STL style): + - structs, classes, namespaces, functions, variables, file names + - Examples: `struct fenwick_tree`, `namespace fast_io`, `solve_case`, `add_edge` +- **Concise yet descriptive**: names must be short for typing speed but clear enough to read under pressure. + - **Good**: `cnt`, `idx`, `res`, `nxt`, `vis`, `dist`. + - **Bad**: `number_of_elements`, `adjacency_list`, `calculated_distance`. ### Types - Prefer `ll` by default to reduce overflow debugging. @@ -61,7 +63,6 @@ Do not commit unformatted code. Before finishing a change: - Style matches the rules above (naming, layout, aliases). - The API is minimal and pasteable. -- No extra dependencies unless clearly worth it. - Complexity and constraints are stated. - Comments explain intent/invariants/edges without being verbose. - The code is not longer than it needs to be. From 0a896d9c81c96bbeaf7940eb4c782fa17209d510 Mon Sep 17 00:00:00 2001 From: manoflearning <77jwk0724@gmail.com> Date: Mon, 29 Dec 2025 20:40:42 +0900 Subject: [PATCH 2/5] docs: change AGENTS.md --- AGENTS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f474e6c..39221dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,7 @@ When adding or editing code here, optimize for contest usage first. - Prefer `ll` by default to reduce overflow debugging. - Use `int` only when clearly safe and beneficial (memory, bitset indexing, array indices, tight constraints). - Avoid implicit narrowing conversions. Cast explicitly at boundaries when mixing types. +- Prefer aliases from `common.hpp` (e.g., `pll`, `pii`) when they match exactly to reduce typing. ## Coding rules - Prefer straightforward implementations over heavy abstractions. @@ -37,8 +38,8 @@ When adding or editing code here, optimize for contest usage first. Write friendly, high-signal documentation in a consistent format. ### 1) Module header (for each reusable component) -Add a short header comment near the top: -- What it does +Add a header comment near the top: +- What it does: add comments so that even content encountered after a long time serves as a reminder. - Complexity (time, memory) - Constraints / gotchas (short) - Optional: where it was verified (contest / problem ID) From 4e07dc212ce2cb3bcbcf9bfa633fe89bfd699d1d Mon Sep 17 00:00:00 2001 From: manoflearning <77jwk0724@gmail.com> Date: Mon, 29 Dec 2025 20:41:04 +0900 Subject: [PATCH 3/5] docs: change AGENTS.md --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 39221dc..ec31b50 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,6 +33,7 @@ When adding or editing code here, optimize for contest usage first. - Avoid unnecessary dynamic polymorphism, complex metaprogramming, and over-engineered designs. - Public-facing helpers should be easy to understand without reading 5 other files. - Template common patterns (I/O, loops, small utilities), but keep templates minimal. +- ICPC team notes have a length limit. Write the code as concisely as possible. ## Comments and documentation (required) Write friendly, high-signal documentation in a consistent format. From 14942f238173133dd3005bb1c46b6e8d62f565c2 Mon Sep 17 00:00:00 2001 From: manoflearning <77jwk0724@gmail.com> Date: Mon, 29 Dec 2025 21:04:13 +0900 Subject: [PATCH 4/5] flow --- src/3-flow/bipartite_matching.cpp | 46 -- src/3-flow/dinics_algorithm.cpp | 63 --- src/3-flow/hopcroft_karp_algorithm.cpp | 61 --- src/3-flow/maximum_flow.cpp | 140 ----- src/3-flow/mcmf.cpp | 59 --- src/3-optimizations/flow.cpp | 482 ++++++++++++++++++ src/{3-flow => 3-optimizations}/hungarian.cpp | 0 7 files changed, 482 insertions(+), 369 deletions(-) delete mode 100644 src/3-flow/bipartite_matching.cpp delete mode 100644 src/3-flow/dinics_algorithm.cpp delete mode 100644 src/3-flow/hopcroft_karp_algorithm.cpp delete mode 100644 src/3-flow/maximum_flow.cpp delete mode 100644 src/3-flow/mcmf.cpp create mode 100644 src/3-optimizations/flow.cpp rename src/{3-flow => 3-optimizations}/hungarian.cpp (100%) diff --git a/src/3-flow/bipartite_matching.cpp b/src/3-flow/bipartite_matching.cpp deleted file mode 100644 index 98db821..0000000 --- a/src/3-flow/bipartite_matching.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "../common/common.hpp" - -// all edges have a capacity of 1 -// O(VE) -const int MAXV = 1010; -int n, m, A[MAXV], B[MAXV]; -vector adj[MAXV]; -bool visited[MAXV]; -void input() { - cin >> n >> m; - for (int i = 1; i <= n; i++) { - int cnt; - cin >> cnt; - while (cnt--) { - int x; - cin >> x; - adj[i].push_back(x); - } - } -} -bool dfs(int a) { - visited[a] = 1; - for (int b : adj[a]) { - if (B[b] == -1 || (!visited[B[b]] && dfs(B[b]))) { - A[a] = b; - B[b] = a; - return 1; - } - } - return 0; -} -int bipartiteMatch() { - memset(A, -1, sizeof(A)); - memset(B, -1, sizeof(B)); - int ret = 0; - for (int i = 1; i <= n; i++) { - memset(visited, 0, sizeof(visited)); - if (dfs(i)) ret++; - } - return ret; -} -int main() { - input(); - int ans = bipartiteMatch(); - cout << ans << '\n'; -} \ No newline at end of file diff --git a/src/3-flow/dinics_algorithm.cpp b/src/3-flow/dinics_algorithm.cpp deleted file mode 100644 index ff5fdcd..0000000 --- a/src/3-flow/dinics_algorithm.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "../common/common.hpp" - -// Dinic's Algorithm -// time complexity : O(V^2 * E) (In networks with unit capacities, it runs in O(min(V^{2/3}, E^{1/2}) * E) time) -const int INF = 1e9 + 7; -const int MAXV = 505; -int N, st = 0, en = MAXV + 1; -vector adj[MAXV + 5]; -int c[MAXV + 5][MAXV + 5], f[MAXV + 5][MAXV + 5]; -int level[MAXV + 5], work[MAXV + 5]; -void input() { - // TODO -} -void bfs() { - memset(level, -1, sizeof(level)); - level[st] = 0; - queue q; - q.push(st); - while (!q.empty()) { - int now = q.front(); - q.pop(); - for (int next : adj[now]) { - if (level[next] == -1 && c[now][next] - f[now][next] > 0) { - level[next] = level[now] + 1; - q.push(next); - } - } - } -} -int dfs(int now, int flow) { - if (now == en) return flow; - for (int &i = work[now]; i < adj[now].size(); i++) { - int next = adj[now][i]; - if (level[next] == level[now] + 1 && c[now][next] - f[now][next] > 0) { - int df = dfs(next, min(c[now][next] - f[now][next], flow)); - if (df > 0) { - f[now][next] += df; - f[next][now] -= df; - return df; - } - } - } - return 0; -} -int dinic() { - int ret = 0; - while (true) { - bfs(); - if (level[en] == -1) break; - memset(work, 0, sizeof(work)); - while (true) { - int flow = dfs(st, INF); - if (flow == 0) break; - ret += flow; - } - } - return ret; -} -int main() { - input(); - int total = dinic(); - cout << total << '\n'; -} \ No newline at end of file diff --git a/src/3-flow/hopcroft_karp_algorithm.cpp b/src/3-flow/hopcroft_karp_algorithm.cpp deleted file mode 100644 index 3e87116..0000000 --- a/src/3-flow/hopcroft_karp_algorithm.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "../common/common.hpp" - -// Bipartite Matching Algorithm -// time complexity : O(E * sqrt(V)) -const int INF = 1e9 + 7; -const int MAXV = 10101; -int n, A[MAXV], B[MAXV], dist[MAXV]; -bool used[MAXV]; -vector adj[MAXV]; -void input() { - // TODO -} -void bfs() { - queue q; - for (int i = 0; i < n; i++) { - if (!used[i]) { - dist[i] = 0; - q.push(i); - } else dist[i] = INF; - } - while (!q.empty()) { - int a = q.front(); - q.pop(); - for (int b : adj[a]) { - if (B[b] != -1 && dist[B[b]] == INF) { - dist[B[b]] = dist[a] + 1; - q.push(B[b]); - } - } - } -} -bool dfs(int a) { - for (int b : adj[a]) { - if (B[b] == -1 || (dist[B[b]] == dist[a] + 1 && dfs(B[b]))) { - used[a] = true; - A[a] = b; - B[b] = a; - return true; - } - } - return false; -} -int hopcroft() { - memset(A, -1, sizeof(A)); - memset(B, -1, sizeof(B)); - int ret = 0; - while (true) { - bfs(); - int flow = 0; - for (int i = 0; i < n; i++) - if (!used[i] && dfs(i)) flow++; - if (flow == 0) break; - ret += flow; - } - return ret; -} -int main() { - input(); - int total = hopcroft(); - cout << total << '\n'; -} \ No newline at end of file diff --git a/src/3-flow/maximum_flow.cpp b/src/3-flow/maximum_flow.cpp deleted file mode 100644 index 5532ed1..0000000 --- a/src/3-flow/maximum_flow.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "../common/common.hpp" - -// 1. Maximum Flow -// Edmonds-Karp algorithm -// time complexity : O(V * E^2) -const int MAXV = 1010; -const int INF = 1e9 + 7; -int n; -ll c[MAXV][MAXV], f[MAXV][MAXV]; -vector adj[MAXV]; -int prv[MAXV]; -void input() { - cin >> n; - for (int i = 0; i < n; i++) { - int u, v, cap; - cin >> u >> v >> cap; - c[u][v] += cap; - adj[u].push_back(v); - // add reverse edge - adj[v].push_back(u); - } -} -void bfs(int st, int en) { - memset(prv, -1, sizeof(prv)); - queue q; - q.push(st); - prv[st] = 0; - while (!q.empty() && prv[en] == -1) { - int now = q.front(); - q.pop(); - for (int next : adj[now]) { - if (prv[next] == -1 && c[now][next] - f[now][next] > 0) { - q.push(next); - prv[next] = now; - } - } - } -} -ll flow(int st, int en) { - ll block = INF; - for (int i = en; i != st; i = prv[i]) { - block = min(block, c[prv[i]][i] - f[prv[i]][i]); - } - for (int i = en; i != st; i = prv[i]) { - f[prv[i]][i] += block; - f[i][prv[i]] -= block; - } - return block; -} -ll maxFlow(int st, int en) { - ll ret = 0; - while (1) { - bfs(st, en); - if (prv[en] == -1) break; - ret += flow(st, en); - } - return ret; -} -int main() { - input(); - ll total = maxFlow(1, n); - cout << total << '\n'; -} - -// 2. Maximum Flow (Struct Edge) -// Edmonds-Karp algorithm -// time complexity : O(V * E^2) -const int MAXV = 1010; -const int INF = 1e9 + 7; -struct edge { - int v; - ll c, f; - edge *dual; // pointer to reverse edge - edge() : edge(-1, 0) {} - edge(int v1, ll c1) : v(v1), c(c1), f(0), dual(nullptr) {} - ll residual() { - return c - f; - } - void addFlow(int f1) { - f += f1; - dual->f -= f1; - } -}; -int n; -vector adj[MAXV + 5]; -int prv[MAXV + 5]; -edge *path[MAXV + 5]; -void input() { - cin >> n; - for (int i = 0; i < n; i++) { - int n1, n2, cap; - cin >> n1 >> n2 >> cap; - edge *e1 = new edge(n2, cap), *e2 = new edge(n1, 0); - e1->dual = e2, e2->dual = e1; - adj[n1].push_back(e1); - adj[n2].push_back(e2); - } -} -void bfs(int st, int en) { - memset(prv, -1, sizeof(prv)); - queue q; - q.push(st); - prv[st] = 0; - while (!q.empty() && prv[en] == -1) { - int now = q.front(); - q.pop(); - for (auto *e : adj[now]) { - int next = e->v; - if (prv[next] == -1 && e->residual() > 0) { - q.push(next); - prv[next] = now; - path[next] = e; - } - } - } -} -ll flow(int st, int en) { - ll block = INF; - for (int i = en; i != st; i = prv[i]) { - block = min(block, path[i]->residual()); - } - for (int i = en; i != st; i = prv[i]) { - path[i]->addFlow(block); - } - return block; -} -ll maxFlow(int st, int en) { - ll ret = 0; - while (1) { - bfs(st, en); - if (prv[en] == -1) break; - ret += flow(st, en); - } - return ret; -} -int main() { - input(); - ll total = maxFlow(1, n); - cout << total << '\n'; -} \ No newline at end of file diff --git a/src/3-flow/mcmf.cpp b/src/3-flow/mcmf.cpp deleted file mode 100644 index a58c1a9..0000000 --- a/src/3-flow/mcmf.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "../common/common.hpp" - -const int INF = 1e9 + 7; -const int MAXV = 1010; -int N, M, st = 0, en = 1001; -int c[MAXV][MAXV], f[MAXV][MAXV]; -int d[MAXV][MAXV], prv[MAXV]; -vector adj[MAXV]; -int mFlow, mCost; -void input() { - // TODO -} -void spfa() { - memset(prv, -1, sizeof(prv)); - vector dist(MAXV, INF); - vector inQ(MAXV); - queue q; - q.push(st); - dist[st] = 0, inQ[st] = true; - while (!q.empty()) { - int now = q.front(); - q.pop(); - inQ[now] = false; - for (int next : adj[now]) { - if (dist[now] + d[now][next] < dist[next] && c[now][next] - f[now][next] > 0) { - dist[next] = dist[now] + d[now][next]; - prv[next] = now; - if (!inQ[next]) { - inQ[next] = true; - q.push(next); - } - } - } - } -} -void flow() { - int block = INF; - for (int i = en; i != st; i = prv[i]) { - block = min(block, c[prv[i]][i] - f[prv[i]][i]); - } - for (int i = en; i != st; i = prv[i]) { - mCost += d[prv[i]][i] * block; - f[prv[i]][i] += block; - f[i][prv[i]] -= block; - } - mFlow += block; -} -void mcmf() { - while (1) { - spfa(); - if (prv[en] == -1) break; - flow(); - } -} -int main() { - input(); - mcmf(); - cout << mFlow << ' ' << mCost; -} \ No newline at end of file diff --git a/src/3-optimizations/flow.cpp b/src/3-optimizations/flow.cpp new file mode 100644 index 0000000..3a12c24 --- /dev/null +++ b/src/3-optimizations/flow.cpp @@ -0,0 +1,482 @@ +#include "../common/common.hpp" + +// Flow templates (0-based). +// what: max flow, min-cost flow, bipartite matching, lr flow +// time: see each struct, memory: O(E) +// constraint: capacities >= 0, mcmf needs no negative cycle +namespace flow { +constexpr ll INF = (1LL << 62); + +// dinic max flow (0-based). +// what: fast max flow with residual graph +// time: O(E V^2) worst, memory: O(E) +// constraint: cap >= 0 +// usage: dinic mf(n); mf.add_edge(u, v, cap); ll f = mf.max_flow(s, t); +struct dinic { + struct edge { + int to, rev; + ll cap; + }; + struct edge_ref { + int u, idx; + }; + + int n; + vector> g; + vector level, work; + + dinic(int n = 0) { init(n); } + + void init(int n_) { + n = n_; + g.assign(n, {}); + } + + edge_ref add_edge(int u, int v, ll cap) { + // goal: add forward + reverse edge + edge a{v, (int)g[v].size(), cap}; + edge b{u, (int)g[u].size(), 0}; + g[u].push_back(a); + g[v].push_back(b); + return {u, (int)g[u].size() - 1}; + } + + ll edge_flow(edge_ref e) const { + // goal: current flow on original edge + const edge &ed = g[e.u][e.idx]; + return g[ed.to][ed.rev].cap; + } + + void clear_edge(edge_ref e) { + // goal: remove edge from residual graph + edge &ed = g[e.u][e.idx]; + edge &rev = g[ed.to][ed.rev]; + ed.cap = 0; + rev.cap = 0; + } + + bool bfs(int s, int t) { + // goal: build level graph + level.assign(n, -1); + queue q; + level[s] = 0; + q.push(s); + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (const auto &e : g[v]) { + if (e.cap == 0 || level[e.to] != -1) continue; + level[e.to] = level[v] + 1; + q.push(e.to); + } + } + return level[t] != -1; + } + + ll dfs(int v, int t, ll f) { + if (v == t || f == 0) return f; + for (int &i = work[v]; i < (int)g[v].size(); i++) { + edge &e = g[v][i]; + if (e.cap == 0 || level[e.to] != level[v] + 1) continue; + // invariant: level strictly increases along augmenting path + ll pushed = dfs(e.to, t, min(f, e.cap)); + if (pushed == 0) continue; + e.cap -= pushed; + g[e.to][e.rev].cap += pushed; + return pushed; + } + return 0; + } + + ll max_flow(int s, int t, ll limit = INF) { + if (s == t) return 0; // edge: no flow needed + ll flow = 0; + while (flow < limit && bfs(s, t)) { + work.assign(n, 0); + while (flow < limit) { + ll pushed = dfs(s, t, limit - flow); + if (pushed == 0) break; + flow += pushed; + } + } + return flow; + } +}; + +// hk bipartite matching (0-based). +// what: maximum matching in bipartite graph +// time: O(E sqrt V), memory: O(E) +// constraint: left [0..n_l-1], right [0..n_r-1] +// usage: hk bm(n_l, n_r); bm.add_edge(l, r); int m = bm.max_matching(); +struct hk { + int n_l, n_r; + vector> g; + vector dist, match_l, match_r; + + hk(int n_l_ = 0, int n_r_ = 0) { init(n_l_, n_r_); } + + void init(int n_l_, int n_r_) { + n_l = n_l_; + n_r = n_r_; + g.assign(n_l, {}); + dist.assign(n_l, 0); + match_l.assign(n_l, -1); + match_r.assign(n_r, -1); + } + + void add_edge(int l, int r) { + // goal: add edge from left to right + g[l].push_back(r); + } + + bool bfs() { + // goal: build layers for shortest augmenting paths + queue q; + fill(dist.begin(), dist.end(), -1); + for (int i = 0; i < n_l; i++) { + if (match_l[i] == -1) { + dist[i] = 0; + q.push(i); + } + } + bool found = false; + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (int r : g[v]) { + int u = match_r[r]; + if (u == -1) { + found = true; + } else if (dist[u] == -1) { + dist[u] = dist[v] + 1; + q.push(u); + } + } + } + return found; + } + + bool dfs(int v) { + for (int r : g[v]) { + int u = match_r[r]; + if (u == -1 || (dist[u] == dist[v] + 1 && dfs(u))) { + match_l[v] = r; + match_r[r] = v; + return true; + } + } + dist[v] = -1; + return false; + } + + int max_matching() { + // goal: compute maximum matching size + fill(match_l.begin(), match_l.end(), -1); + fill(match_r.begin(), match_r.end(), -1); + int match = 0; + while (bfs()) { + for (int i = 0; i < n_l; i++) { + if (match_l[i] == -1 && dfs(i)) match++; + } + } + return match; + } +}; + +// mcmf min-cost max-flow (0-based). +// what: min-cost flow with negative costs via potentials +// time: O(F E log V), memory: O(E) +// constraint: no negative cycle reachable from s +// usage: mcmf mf(n); mf.add_edge(u, v, cap, cost); pll r = mf.min_cost_max_flow(s, t); +// usage: init_pot = false if all costs >= 0 (faster) +struct mcmf { + struct edge { + int to, rev; + ll cap, cost; + }; + struct edge_ref { + int u, idx; + }; + + int n; + vector> g; + + mcmf(int n = 0) { init(n); } + + void init(int n_) { + n = n_; + g.assign(n, {}); + } + + edge_ref add_edge(int u, int v, ll cap, ll cost) { + // goal: add forward + reverse edge with costs + edge a{v, (int)g[v].size(), cap, cost}; + edge b{u, (int)g[u].size(), 0, -cost}; + g[u].push_back(a); + g[v].push_back(b); + return {u, (int)g[u].size() - 1}; + } + + ll edge_flow(edge_ref e) const { + // goal: current flow on original edge + const edge &ed = g[e.u][e.idx]; + return g[ed.to][ed.rev].cap; + } + + void clear_edge(edge_ref e) { + // goal: remove edge from residual graph + edge &ed = g[e.u][e.idx]; + edge &rev = g[ed.to][ed.rev]; + ed.cap = 0; + rev.cap = 0; + } + + pll min_cost_max_flow(int s, int t, ll max_f = INF, bool init_pot = true) { + ll flow = 0, cost = 0; + vector pot(n, 0), dist(n); + vector pv(n), pe(n); + + if (init_pot) { + // goal: initial potentials for negative costs + vector d(n, INF); + vector in_q(n, 0); + queue q; + d[s] = 0; + q.push(s); + in_q[s] = 1; + while (!q.empty()) { + int v = q.front(); + q.pop(); + in_q[v] = 0; + for (const auto &e : g[v]) { + if (e.cap == 0) continue; + if (d[v] + e.cost < d[e.to]) { + d[e.to] = d[v] + e.cost; + if (!in_q[e.to]) { + in_q[e.to] = 1; + q.push(e.to); + } + } + } + } + for (int i = 0; i < n; i++) + if (d[i] < INF) pot[i] = d[i]; + } + + while (flow < max_f) { + // goal: shortest path in reduced costs + fill(dist.begin(), dist.end(), INF); + dist[s] = 0; + priority_queue, vector>, greater>> pq; + pq.push({0, s}); + while (!pq.empty()) { + auto [d, v] = pq.top(); + pq.pop(); + if (d != dist[v]) continue; + for (int i = 0; i < (int)g[v].size(); i++) { + const auto &e = g[v][i]; + if (e.cap == 0) continue; + ll nd = d + e.cost + pot[v] - pot[e.to]; + if (nd < dist[e.to]) { + dist[e.to] = nd; + pv[e.to] = v; + pe[e.to] = i; + pq.push({nd, e.to}); + } + } + } + if (dist[t] == INF) break; // edge: no more augmenting paths + for (int i = 0; i < n; i++) + if (dist[i] < INF) pot[i] += dist[i]; + + ll add = max_f - flow; + for (int v = t; v != s; v = pv[v]) { + const auto &e = g[pv[v]][pe[v]]; + add = min(add, e.cap); + } + for (int v = t; v != s; v = pv[v]) { + auto &e = g[pv[v]][pe[v]]; + e.cap -= add; + g[v][e.rev].cap += add; + cost += add * e.cost; + } + flow += add; + } + + return {flow, cost}; + } +}; + +// lr_dinic (0-based). +// what: max flow with edge lower/upper bounds +// time: dominated by dinic, memory: O(E) +// constraint: 0 <= lo <= hi, single-use (call init(n) to reuse) +// usage: lr_dinic f(n); int id = f.add_edge(u, v, lo, hi); auto [ok, v] = f.max_flow(s, t); +struct lr_dinic { + struct edge_info { + dinic::edge_ref ref; + ll lo; + }; + + int n; + dinic mf; + vector demand; + vector edges; + + lr_dinic(int n = 0) { init(n); } + + void init(int n_) { + n = n_; + mf.init(n + 2); + demand.assign(n, 0); + edges.clear(); + } + + int add_edge(int u, int v, ll lo, ll hi) { + // goal: store lower bounds via node demands + demand[u] -= lo; + demand[v] += lo; + edges.push_back({mf.add_edge(u, v, hi - lo), lo}); + return (int)edges.size() - 1; + } + + ll edge_flow(int id) const { + // goal: actual flow with lower bound restored + return edges[id].lo + mf.edge_flow(edges[id].ref); + } + + ll add_demands(vector &aux) { + // goal: connect ss/tt for feasible circulation + ll total = 0; + int ss = n, tt = n + 1; + for (int i = 0; i < n; i++) { + if (demand[i] > 0) { + aux.push_back(mf.add_edge(ss, i, demand[i])); + total += demand[i]; + } else if (demand[i] < 0) { + aux.push_back(mf.add_edge(i, tt, -demand[i])); + } + } + return total; + } + + bool feasible() { + vector aux; + aux.reserve(n); + ll total = add_demands(aux); + int ss = n, tt = n + 1; + ll flow = mf.max_flow(ss, tt); + for (auto ref : aux) mf.clear_edge(ref); + return flow == total; + } + + pair max_flow(int s, int t) { + if (s == t) return {feasible(), 0}; // edge: trivial s == t + vector aux; + aux.reserve(n + 1); + int ss = n, tt = n + 1; + auto ts = mf.add_edge(t, s, INF); + ll total = add_demands(aux); + ll flow = mf.max_flow(ss, tt); + if (flow != total) { + mf.clear_edge(ts); + for (auto ref : aux) mf.clear_edge(ref); + return {false, 0}; + } + ll base = mf.edge_flow(ts); + mf.clear_edge(ts); + for (auto ref : aux) mf.clear_edge(ref); + ll extra = mf.max_flow(s, t); + return {true, base + extra}; + } +}; + +// lr_mcmf (0-based). +// what: max flow with edge lower/upper bounds + min cost +// time: dominated by mcmf, memory: O(E) +// constraint: 0 <= lo <= hi, no negative cycle, single-use (call init(n) to reuse) +// usage: lr_mcmf f(n); f.add_edge(u, v, lo, hi, cost); auto [ok, r] = f.max_flow(s, t); +struct lr_mcmf { + struct edge_info { + mcmf::edge_ref ref; + ll lo; + }; + + int n; + mcmf mf; + vector demand; + vector edges; + + lr_mcmf(int n = 0) { init(n); } + + void init(int n_) { + n = n_; + mf.init(n + 2); + demand.assign(n, 0); + edges.clear(); + } + + int add_edge(int u, int v, ll lo, ll hi, ll cost) { + // goal: store lower bounds via node demands + demand[u] -= lo; + demand[v] += lo; + edges.push_back({mf.add_edge(u, v, hi - lo, cost), lo}); + return (int)edges.size() - 1; + } + + ll edge_flow(int id) const { + // goal: actual flow with lower bound restored + return edges[id].lo + mf.edge_flow(edges[id].ref); + } + + ll add_demands(vector &aux) { + // goal: connect ss/tt for feasible circulation + ll total = 0; + int ss = n, tt = n + 1; + for (int i = 0; i < n; i++) { + if (demand[i] > 0) { + aux.push_back(mf.add_edge(ss, i, demand[i], 0)); + total += demand[i]; + } else if (demand[i] < 0) { + aux.push_back(mf.add_edge(i, tt, -demand[i], 0)); + } + } + return total; + } + + pair feasible(bool init_pot = true) { + vector aux; + aux.reserve(n); + ll total = add_demands(aux); + int ss = n, tt = n + 1; + pll res = mf.min_cost_max_flow(ss, tt, total, init_pot); + for (auto ref : aux) mf.clear_edge(ref); + return {res.first == total, res.second}; + } + + pair max_flow(int s, int t, bool init_pot = true) { + if (s == t) { + auto r = feasible(init_pot); + return {r.first, {0, r.second}}; + } + vector aux; + aux.reserve(n + 1); + int ss = n, tt = n + 1; + auto ts = mf.add_edge(t, s, INF, 0); + ll total = add_demands(aux); + pll res = mf.min_cost_max_flow(ss, tt, total, init_pot); + if (res.first != total) { + mf.clear_edge(ts); + for (auto ref : aux) mf.clear_edge(ref); + return {false, {0, 0}}; + } + ll base = mf.edge_flow(ts); + ll cost = res.second; + mf.clear_edge(ts); + for (auto ref : aux) mf.clear_edge(ref); + pll extra = mf.min_cost_max_flow(s, t, INF, init_pot); + return {true, {base + extra.first, cost + extra.second}}; + } +}; + +} // namespace flow diff --git a/src/3-flow/hungarian.cpp b/src/3-optimizations/hungarian.cpp similarity index 100% rename from src/3-flow/hungarian.cpp rename to src/3-optimizations/hungarian.cpp From a68eb0eb2107e0718e7326357b4677356c90c77c Mon Sep 17 00:00:00 2001 From: manoflearning <77jwk0724@gmail.com> Date: Mon, 29 Dec 2025 21:11:40 +0900 Subject: [PATCH 5/5] flow --- src/3-optimizations/flow.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/3-optimizations/flow.cpp b/src/3-optimizations/flow.cpp index 3a12c24..8256d65 100644 --- a/src/3-optimizations/flow.cpp +++ b/src/3-optimizations/flow.cpp @@ -451,13 +451,13 @@ struct lr_mcmf { int ss = n, tt = n + 1; pll res = mf.min_cost_max_flow(ss, tt, total, init_pot); for (auto ref : aux) mf.clear_edge(ref); - return {res.first == total, res.second}; + return {res.fr == total, res.sc}; } pair max_flow(int s, int t, bool init_pot = true) { if (s == t) { auto r = feasible(init_pot); - return {r.first, {0, r.second}}; + return {r.fr, {0, r.sc}}; } vector aux; aux.reserve(n + 1); @@ -465,17 +465,17 @@ struct lr_mcmf { auto ts = mf.add_edge(t, s, INF, 0); ll total = add_demands(aux); pll res = mf.min_cost_max_flow(ss, tt, total, init_pot); - if (res.first != total) { + if (res.fr != total) { mf.clear_edge(ts); for (auto ref : aux) mf.clear_edge(ref); return {false, {0, 0}}; } ll base = mf.edge_flow(ts); - ll cost = res.second; + ll cost = res.sc; mf.clear_edge(ts); for (auto ref : aux) mf.clear_edge(ref); pll extra = mf.min_cost_max_flow(s, t, INF, init_pot); - return {true, {base + extra.first, cost + extra.second}}; + return {true, {base + extra.fr, cost + extra.sc}}; } };