Codeforces Round #310 (Div.1) E - Case of Computer Network
解法
二重辺連結成分分解をしてくださいと書いてあるので、先にやっておく。
ただし、与えられるグラフは単純グラフとは限らないので、多重辺にも対応しておく。
するとグラフは森になるので、結局木の上で元の問題が解ければ良い。
ある辺について、どちらに向き付すべきかを考えよう。
(各)木について適当に根を決めて、根付き木にする。
ある頂点 v と、その親に伸びている辺について、その部分木に含まれる s[i] の数、d[i] の数、s[i] と d[i] の LCA の数をそれぞれ a, b, c とする(s[i], d[i], LCA の値として同じものがあっても、それらは何度も数える)。
すると、今注目している辺は、a - c が正のとき、上に伸びていなければならないし、逆に b - c が正のとき、下に伸びていなければならない。
これは、v の部分木内で完結している s[i], d[i] のペアの数が部分木に含まれる LCA の数に対応していることを考えれば容易にわかる。
これらがともに正となるとき、与えられた条件を満たす向きづけは存在しない。それ以外のときは、向き付が存在する。
ソースコード
#include <bits/stdc++.h> using namespace std; struct edge { int from, to; }; using edges = std::vector<edge>; using graph = std::vector<edges>; void add_edge(graph& g, int from, int to) { g[from].push_back(edge{from, to}); g[to].push_back(edge{to, from}); } class lowlink { public: lowlink(graph const& g) : ord(g.size()), low(g.size()) { const int n = g.size(); std::vector<bool> visited(n); int cnt = 0; for(int i = 0; i < n; ++i) { if(!visited[i]) { dfs(g, i, -1, cnt, visited); } } } std::vector<edge> get_bridges() const { return bridges; } bool is_bridge(int u, int v) const { assert(0 <= u && u < (int)ord.size() && 0 <= v && v < (int)ord.size()); if(ord[u] > ord[v]) std::swap(u, v); return ord[u] < low[v]; } private: void dfs(graph const& g, int v, int prev, int& cnt, std::vector<bool>& visited) { visited[v] = true; low[v] = ord[v] = cnt++; int cnt2 = 0, pcnt = 0; for(auto& e : g[v]) { if((e.to != prev || (e.to == prev && pcnt == 1)) && visited[e.to]) { low[v] = min(low[v], ord[e.to]); } else if(!visited[e.to]) { cnt2++; dfs(g, e.to, v, cnt, visited); low[v] = min(low[v], low[e.to]); if(is_bridge(v, e.to)) { bridges.push_back(edge{min(v, e.to), max(v, e.to)}); } } pcnt += e.to == prev; } } private: std::vector<edge> bridges; std::vector<int> ord, low; }; // require: lowlink class biconnected_component { public: biconnected_component(graph const& g_) : g(g_), helper(g_), belongs_(g_.size(), -1) { for(auto&& b : helper.get_bridges()) { add_component(b.from), add_component(b.to); } add_component(0); } graph build() { // if not connected, result is forest graph res(cmps.size()); auto bridges = helper.get_bridges(); for(auto& b : bridges) { add_edge(res, belongs(b.from), belongs(b.to)); } return res; } int belongs(int u) const { assert(0 <= u && u < (int)belongs_.size()); return belongs_[u]; } private: void fill_component(int c, int u) { cmps[c].emplace_back(u); belongs_[u] = c; for(auto& e : g[u]) { if(belongs_[e.to] >= 0 || helper.is_bridge(u, e.to)) continue; fill_component(c, e.to); } } void add_component(int u) { if(belongs_[u] >= 0) return; cmps.emplace_back(); assert(cmps.size() >= 1u); fill_component(cmps.size() - 1, u); } private: graph g; lowlink helper; std::vector<int> belongs_; std::vector<std::vector<int>> cmps; }; class lowest_common_ancestor { public: lowest_common_ancestor(graph const& g/* int root*/) : n(g.size()), log_n(std::ceil(std::log2(g.size())) + 1), parent(log_n, std::vector<int>(n, -1)), depth_(n) { for(int i = 0; i < n; ++i) { if(parent[0][i] == -1) dfs(g, i, -1, 0); } for(int k = 0; k + 1 < log_n; ++k) { for(int v = 0; v < n; ++v) { if(parent[k][v] < 0) parent[k + 1][v] = -1; else parent[k + 1][v] = parent[k][parent[k][v]]; } } } int query(int u, int v) const { if(depth_[u] > depth_[v]) { std::swap(u, v); } for(int k = 0; k < log_n; ++k) { if((depth_[v] - depth_[u]) >> k & 1) { v = parent[k][v]; } } if(u == v) { return u; } for(int k = log_n - 1; k >= 0; --k) { if(parent[k][u] != parent[k][v]) { u = parent[k][u]; v = parent[k][v]; } } return parent[0][u]; } private: void dfs(graph const& g, int v, int p, int d) { parent[0][v] = p; depth_[v] = d; for(auto& e : g[v]) { if(e.to != p) { dfs(g, e.to, v, d + 1); } } } private: int const n, log_n; std::vector<std::vector<int>> parent; std::vector<int> depth_; }; class union_find { public: union_find(int n) : par(n, -1) {} int root(int x) { return par[x] < 0 ? x : par[x] = root(par[x]); } bool same(int x, int y) { return root(x) == root(y); } void unite(int x, int y) { x = root(x), y = root(y); if(x == y) return; if(par[x] > par[y]) swap(x, y); par[x] += par[y]; par[y] = x; } private: vector<int> par; }; int main() { int n, m, q; cin >> n >> m >> q; graph g(n); for(int i = 0; i < m; ++i) { int v, u; cin >> v >> u; add_edge(g, v - 1, u - 1); } vector<int> s(q), d(q); for(int i = 0; i < q; ++i) { cin >> s[i] >> d[i]; s[i]--, d[i]--; } biconnected_component bicc(g); auto t = bicc.build(); const int tn = t.size(); lowest_common_ancestor lca(t); union_find uf(tn); for(auto& es : t) { for(auto& e : es) { uf.unite(e.from, e.to); } } vector<int> sum1(tn), sum2(tn), sum3(tn); for(int i = 0; i < q; ++i) { s[i] = bicc.belongs(s[i]), d[i] = bicc.belongs(d[i]); if(!uf.same(s[i], d[i])) { cout << "No" << endl; return 0; } sum1[s[i]] += 1, sum2[d[i]] += 1; sum3[lca.query(s[i], d[i])] += 1; } vector<bool> visited(tn); function<bool(int, int)> dfs = [&] (int v, int p) { bool res = true; visited[v] = true; for(auto& e : t[v]) { if(e.to == p) continue; res &= dfs(e.to, v); sum1[v] += sum1[e.to], sum2[v] += sum2[e.to], sum3[v] += sum3[e.to]; } return res & (sum1[v] - sum3[v] <= 0 || sum2[v] - sum3[v] <= 0); }; bool ans = true; for(int i = 0; i < tn; ++i) { if(visited[uf.root(i)]) continue; ans &= dfs(uf.root(i), -1); } cout << (ans ? "Yes" : "No") << endl; }
感想
単純グラフじゃないのはいいとして、連結である保証すら無いのでいじわるだなあと思った(本質じゃないんだから手を抜いてくれても良いんじゃないかなあ)。
ライブラリがないと実装するの大変そう。ICPCだったら最悪。
後で気がついたけど、テストケースがダメダメっぽい。部分木の総和を求め忘れてても通ってしまっていた。