classDfsTour { int n, cnt; std::vector<int> l, r; std::vector<std::vector<int>> e; public: DfsTour(int _n) : n(_n), e(n), l(n), r(n), cnt(0) {} voidaddEdge(int u, int v){ if (u == v) return; e[u].emplace_back(v); e[v].emplace_back(u); } voiddfs(int u, int fa){ l[u] = ++cnt; for (auto v : e[u]) if (v != fa) { dfs(v, u); } r[u] = cnt; } };
其中 u 的子树的编号正好是区间 \([l_u, r_u]\),注意不可能有交叉的情况!
关于子树的问题,可以考虑一下 dfs 序。
在节点权值可修改的情况下,查询某个子树里的所有点权和。
由于在上述 dfs 序中子树 x 是连续的一段 \([l_x, r_x]\),所以用树状数组:单点更新,区间查询。
节点 X 到 Y 的最短路上所有点权都加上一个数 W,查询某个子树里的所有点权和。 可以理解为更新 4 段区间,根节点到 X,根节点到 Y,根节点到 lca(X, Y),根节点到 fa[lca(X, Y)],可以用 线段树 或 带区间更新的树状数组。
有根树的 Euler 序列(长度为 2n - 1)
1 2 3 4 5 6 7 8 9 10 11 12 13
// 以 rt 为根的树,只记录进入的 Euler 序(长度为 2n - 1) std::vector<int> EulerTour(std::vector<std::vector<int>>& e, int rt){ std::vector<int> r; std::function<void(int, int)> dfs = [&](int u, int fa) -> void { r.emplace_back(u); for (auto v : e[u]) if (v != fa) { dfs(v, u); r.emplace_back(u); } }; dfs(rt, rt); return r; }
#include<bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = longlong; usingnamespace std;
// 为了代码简洁,树的编号以 1 开始 classLCA { int n; std::vector<int> fa, dep, sz, son, top; public: LCA(std::vector<std::vector<int>> &e, int rt = 1) : n(e.size()) { fa.resize(n); dep.resize(n); sz.resize(n); son.resize(n); fa[rt] = rt; dep[rt] = 0; std::function<int(int)> pdfs = [&](int u) -> int { sz[u] = 1; for (auto v : e[u]) if (v != fa[u]) { dep[v] = dep[u] + 1; fa[v] = u; sz[u] += pdfs(v); if (sz[v] > sz[son[u]]) son[u] = v; } return sz[u]; }; top.resize(n); std::function<void(int, int)> dfs = [&](int u, int t) -> void { top[u] = t; if (son[u] == 0) return; dfs(son[u], t); for (auto v : e[u]) if (v != fa[u] && v != son[u]) dfs(v, v); }; pdfs(rt); dfs(rt, rt); } intlca(int u, int v){ while (top[u] != top[v]) { if (dep[top[u]] > dep[top[v]]) { u = fa[top[u]]; } else { v = fa[top[v]]; } } return dep[u] < dep[v] ? u : v; } };
intmain(){ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int n, m, rt; std::cin >> n >> m >> rt; std::vector<std::vector<int>> e(n + 1); for (int i = 1; i < n; ++i) { int u, v; std::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } LCA g(e, rt); for (int i = 0; i < m; ++i) { int x, y; std::cin >> x >> y; std::cout << g.lca(x, y) << "\n"; } return0; }
#include<bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = longlong;
#include<bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = longlong;
// 为了代码简洁,树的编号以 1 开始。 std::vector<int> dsuOnTree(std::vector<std::vector<int>> &e, int rt = 1){ int n = e.size(); // 预处理出重儿子 std::vector<int> sz(n), son(n); std::function<void(int, int)> pdfs = [&](int u, int fa) -> void { for (auto v : e[u]) if (v != fa) { pdfs(v, u); if (sz[v] > sz[son[u]]) son[u] = v; } sz[u] = sz[son[u]] + 1; }; std::vector<int> ans(n); std::function<std::vector<int>(int, int)> dfs = [&](int u, int fa) -> std::vector<int> { if (son[u] == 0) { ans[u] = 0; return {1}; } auto a = dfs(son[u], u); ans[u] = ans[son[u]]; for (auto v : e[u]) if (v != fa && v != son[u]) { auto tmp = dfs(v, u); // 这里需要对齐 for (int ai = a.size() - 1, ti = tmp.size() - 1; ti >= 0; --ti, --ai) { a[ai] += tmp[ti]; if (a[ai] > a[ans[u]] || (a[ai] == a[ans[u]] && ai > ans[u])) { ans[u] = ai; } } } a.emplace_back(1); if (a[ans[u]] == 1) ans[u] = sz[u] - 1; return a; }; pdfs(rt, 0); dfs(rt, 0); for (int i = 1; i < n; ++i) ans[i] = sz[i] - 1 - ans[i]; return ans; }
intmain(){ //freopen("in", "r", stdin); std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int n; std::cin >> n; std::vector<std::vector<int>> e(n + 1); for (int i = 1; i < n; ++i) { int u, v; std::cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } auto r = dsuOnTree(e); for (int i = 1; i <= n; ++i) std::cout << r[i] << "\n"; return0; }
// 为了代码简洁,树的编号以 1 开始,参考:https://www.cnblogs.com/zwfymqz/p/9683124.html std::vector<LL> dsuOnTree(std::vector<std::vector<int>> &e, std::vector<int> &a, int rt = 1){ int n = a.size(); // 预处理出重儿子 std::vector<int> sz(n), son(n), cnt(n); std::function<int(int, int)> pdfs = [&](int u, int fa) -> int { sz[u] = 1; for (auto v : e[u]) if (v != fa) { sz[u] += pdfs(v, u); if (sz[v] > sz[son[u]]) son[u] = v; } return sz[u]; }; // 这个函数具体问题具体分析 std::vector<LL> ans(n); int mx = 0, Son = 0; LL sm = 0; std::function<void(int, int)> deal = [&](int u, int fa) -> void { ++cnt[a[u]]; if (cnt[a[u]] > mx) { mx = cnt[a[u]]; sm = a[u]; } elseif (cnt[a[u]] == mx) { sm += a[u]; } for (auto v : e[u]) if (v != fa && v != Son) { deal(v, u); } }; std::function<void(int, int)> del = [&](int u, int fa) -> void { --cnt[a[u]]; for (auto v : e[u]) if (v != fa) del(v, u); }; std::function<void(int, int, bool)> dfs = [&](int u, int fa, bool save) -> void { for (auto v : e[u]) if (v != fa && v != son[u]) { dfs(v, u, 0); // 先计算轻边贡献,但最终要消除影响,防止轻边互相干扰 } if (son[u]) dfs(son[u], u, 1); // 统计重儿子的贡献,但不消除影响 Son = son[u]; deal(u, fa); // 暴力处理除重儿子外的贡献 Son = 0; ans[u] = sm; if (!save) { del(u, fa); sm = 0; mx = 0; } }; pdfs(rt, rt); dfs(rt, rt, 1); return ans; }
using Edge = std::tuple<int, int, int>; LL LiuZhu(std::vector<Edge> e, int n, int rt){ // e 中无自环 LL ans = 0; while (1) { // 寻找入边权值最小的边 std::vector<int> in(n, INT_MAX), pre(n, -1); for (auto [u, v, w] : e) if (u != v && in[v] > w) { in[v] = w; pre[v] = u; } // 判定是否无解 for (int i = 0; i < n; ++i) { if (i != rt && pre[i] == -1) return-1; } // 判定是否有环 int cnt = 0; std::vector<int> vis(n, -1), id(n, -1); for (int i = 0; i < n; ++i) if (i != rt) { ans += in[i]; int v = i; // 注意到可能出现 6 型的路径,所以两个指标很必要 while (vis[v] != i && id[v] == -1 && v != rt) { vis[v] = i; v = pre[v]; } if (id[v] == -1 && v != rt) { int u = v; do { id[u] = cnt; u = pre[u]; } while (u != v); ++cnt; } } if (cnt == 0) break; // 更新节点和边,也可以重开一个 vector,然后 swap 一下 for (int i = 0; i < n; ++i) if (id[i] == -1) id[i] = cnt++; for (auto &[u, v, w] : e) { if (id[u] != id[v]) w -= in[v]; u = id[u]; v = id[v]; } rt = id[rt]; n = cnt; } return ans; }
const LL M = 998244353; // 图以 0 开始编号 LL color2(std::vector<std::vector<int>>& e){ int n = e.size(); std::vector<int> val(n); auto bfs = [&](int x) { std::queue<int> Q; Q.push(x); val[x] = 1; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (auto v : e[u]) { if (val[v]) { if (val[v] != -val[u]) return0; } else { val[v] = -val[u]; Q.push(v); } } } return2; }; LL r = 1; for (int i = 0; i < n; ++i) if (val[i] == 0) { r = r * bfs(i) % M; if (r == 0) return0; } return r; }
#include<bits/stdc++.h> #include<boost/multiprecision/cpp_int.hpp> using BINT = boost::multiprecision::cpp_int;
// chromaticPoly of a tree with n node std::vector<BINT> chromaticPoly(int n){ std::vector<BINT> r(n + 1); BINT now{n % 2 == 1 ? 1 : -1}; for (int i = 0; i < n; ++i) { r[i + 1] = now; now = -now * (n - 1 - i) / (i + 1); } return r; } std::vector<BINT> colorConnect(std::vector<std::set<int>> e){ int n = e.size(); std::vector<bool> v1(n), v2(n); auto r = chromaticPoly(n); // 可以先预处理出来 auto subtract = [](std::vector<BINT> &a, std::vector<BINT> b) { for (int i = 0; i != b.size(); ++i) a[i] -= b[i]; }; std::queue<int> Q; Q.push(0); v1[0] = 1; auto enow = e; while (!Q.empty()) { int u = Q.front(); v2[u] = 1; Q.pop(); for (auto v : e[u]) if (!v2[v]) { if (v1[v]) { std::vector<std::set<int>> ed; std::vector<int> p(n); for (int i = 0, now = 0; i < n; ++i) { if (i != u && i != v) { p[i] = now++; } else p[i] = n - 2; } for (int i = 0; i < n; ++i) if (i != u && i != v) { std::set<int> tmp; for (auto x : enow[i]) tmp.insert(p[x]); ed.emplace_back(tmp); } enow[u].erase(v); enow[v].erase(u); std::set<int> tmp; for (auto x : enow[u]) tmp.insert(p[x]); for (auto x : enow[v]) tmp.insert(p[x]); ed.emplace_back(tmp); subtract(r, colorConnect(ed)); } else { Q.push(v); v1[v] = 1; } } e = enow; } return r; } std::vector<BINT> color(std::vector<std::set<int>> &e){ int n = e.size(); std::vector<bool> vis(n); auto connect = [&](int x) { std::vector<bool> visc(n); std::queue<int> Q; Q.push(x); visc[x] = 1; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (auto v : e[u]) if (!visc[v]) { visc[v] = 1; Q.push(v); } } std::vector<int> p(n); for (int i = 0, now = 0; i < n; ++i) if (visc[i]) { p[i] = now++; } std::vector<std::set<int>> ec; for (int i = 0; i < n; ++i) if (visc[i]) { std::set<int> tmp; for (auto x : e[i]) tmp.insert(p[x]); ec.emplace_back(tmp); vis[i] = 1; } return ec; }; auto mul = [](std::vector<BINT> &a, std::vector<BINT> b) { std::vector<BINT> c(a.size() + b.size() - 1); for (int i = 0; i != a.size(); ++i) { for (int j = 0; j != b.size(); ++j) { c[i + j] += a[i] * b[j]; } } return c; }; std::vector<BINT> r(1, 1); for (int i = 0; i < n; ++i) if (!vis[i]) { r = mul(r, colorConnect(connect(i))); } return r; }
intmain(){ // freopen("in","r",stdin); std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int cas = 1; std::cin >> cas; while (cas--) { int n, m; std::cin >> n >> m; std::vector<std::set<int>> e(n); while (m--) { int u, v; std::cin >> u >> v; --u; --v; e[u].insert(v); e[v].insert(u); } for (auto x : color(e)) std::cout << x << " "; std::cout << std::endl; } return0; }
structScc { int n, nScc; std::vector<int> vis, color, order; std::vector<std::vector<int>> e, e2; Scc(int _n) : n(_n * 2) { nScc = 0; e.resize(n); e2.resize(n); vis.resize(n); color.resize(n); } voidaddEdge(int u, int v){ e[u].emplace_back(v); e2[v].emplace_back(u); } voiddfs(int u){ vis[u] = true; for (auto v : e[u]) if (!vis[v]) dfs(v); order.emplace_back(u); } voiddfs2(int u){ color[u] = nScc; for (auto v : e2[u]) if (!color[v]) dfs2(v); } voidKosaraju(){ for (int i = 0; i < n; ++i) if (!vis[i]) dfs(i); for (auto it = order.rbegin(); it != order.rend(); ++it) if (!color[*it]) { ++nScc; dfs2(*it); } } };
classDinic { int n; std::vector<std::unordered_map<int, int>> g; std::vector<std::unordered_map<int, int>::iterator> cur; std::vector<int> h; // h[i] 表示 bfs 从 s 到 i 的距离,如果找到了 t,那么就说明找到了增广路。 boolbfs(int s, int t){ h.assign(n, -1); std::queue<int> Q; h[s] = 0; Q.push(s); while (!Q.empty()) { int u = Q.front(); Q.pop(); for (auto [v, c] : g[u]) { if (c > 0 && h[v] == -1) { h[v] = h[u] + 1; Q.push(v); } } } return h[t] != -1; } // f 表示从 u 点出发拥有的最大流量,输出的是 u 到 t 的最大流量 intdfs(int u, int t, int f){ if (u == t || f == 0) return f; int r = f; for (auto &it = cur[u]; it != g[u].end(); ++it) { int v = it->first; if (it->second > 0 && h[v] == h[u] + 1) { int a = dfs(v, t, std::min(r, it->second)); it->second -= a; g[v][u] += a; r -= a; if (r == 0) return f; } } return f - r; } public: Dinic(int _n) : n(_n), g(n), cur(n) {} voidaddEdge(int u, int v, int c){ // 注意这里一定要这样! if (u == v) return; g[u][v] += c; g[v][u] += 0; } intmaxFlow(int s, int t){ int r = 0; while (bfs(s, t)) { for (int i = 0; i < n; ++i) cur[i] = g[i].begin(); r += dfs(s, t, INT_MAX); } return r; } };
// 做完 minCut 之后原图就毁了 classStoerWagner { int n; std::vector<std::vector<int>> g; std::vector<int> del; voidmerge(int s, int t){ del[s] = 1; for (int i = 0; i < n; ++i) { g[i][t] = (g[t][i] += g[s][i]); } } public: StoerWagner(int _n) : n(_n), del(n), g(n, std::vector<int>(n)) {} voidaddEdge(int u, int v, int c){ if (u == v) return; g[u][v] += c; g[v][u] += c; } intminCut(){ auto f = [&](int cnt, int &s, int &t) -> int { std::vector<int> vis(n), d(n); auto push = [&](int x){ vis[x] = 1; d[x] = 0; for (int i = 0; i < n; ++i) if (!del[i] && !vis[i]) d[i] += g[x][i]; }; for (int i = 0; i < cnt; ++i) { push(t); s = t; t = std::max_element(d.begin(), d.end()) - d.begin(); } return d[t]; }; int s = 0, t = 0, r = INT_MAX; for (int i = n - 1; i > 0; --i) { r = std::min(r, f(i, s, t)); merge(s, t); } return r == INT_MAX ? 0 : r; } };
using Edge = std::tuple<int, int, int>; LL StoerWagner(std::vector<Edge> e, int n){ auto f = [&]() -> std::tuple<int, int, int> { std::priority_queue<std::pair<int, int>> Q; std::vector<std::vector<std::pair<int, int>>> in(n); for (auto [u, v, w] : e) if (u != v) in[v].emplace_back(u, w); std::vector<int> del(n), d(n); auto push = [&](int x){ for (auto [i, c] : in[x]) if (!del[i]) { Q.push({d[i] += c, i}); } del[x] = 1; }; int s, t = 0; for (int i = 1; i < n; ++i) { push(t); s = t; while (1) { if (Q.empty()) { for (int i = 0; i < n; ++i) if (!del[i]) Q.push({d[i], i}); } t = Q.top().second; Q.pop(); if (!del[t]) break; } } return {d[t], s, t}; }; int s = 0, t = 0, r = INT_MAX; while(n > 1 && r > 0) { auto [dt, s, t] = f(); r = std::min(r, dt); std::vector<int> id(n); int cnt = -1; for (int i = 0; i < n; ++i) if (i != s && i != t) id[i] = ++cnt; id[s] = id[t] = ++cnt; for (auto &[u, v, w] : e) { u = id[u]; v = id[v]; } --n; } return r == INT_MAX ? 0 : r; }
最小费用最大流
在最大流的前提下,追求费用最小。一般通用的做法:每次找一条费用最小的可行流。 反向边的费用是原边的相反数,这样就会出现负边,但是因此初始反向边容量为 0,所以初始情况可以理解为图中没有负边。从源点到汇点的费用必然是非负的(因为我们每次走最小费用,所以每次的费用都是非降的,而初始没有负边。)当然这并不代表途中没有经过负边。至于为什么可以用 Dijkstra,很多博客都有介绍。下面代码中 h 为真实的距离,注意到 h[s]始终为 0,对于同一个点,每次的真实距离不减,它将作为下一次求最短路的势。这种思想也称为 Johnson 最短路径算法算法。可以 \(O(n m \log m)\) 解决全源最短路问题。