DDCC C. グラフいじり
DDCC 2017 本戦 C. グラフいじり
C: グラフいじり - DISCO presents ディスカバリーチャンネル コードコンテスト2017 本戦 | AtCoder
感想
dfsの計算量を間違えていた。$\ O(m(n + m))\ $。これは$\ O(n^4)\ $なので、$\ TLE\ $する。仕方がないので無理やり通した。
解法
まずはある有向グラフが与えられたときに、それが条件(どのサイクルの距離の総和も$\ 0\ $であること)を満たしているかどうかを判定することを考える。条件を満たしているグラフの任意の異なる2点$\ S, T\ $を見ると、$\ S\ $から$\ T\ $に向かう任意のパスの距離はすべて一定になっているはずである。なぜなら、もし2つのパスが存在してその距離がそれぞれ$\ d_1, d_2\ (d_1 \neq d_2)\ $となっているならば、条件より存在する$\ T\ $から$\ S\ $へのある共通のパスを用いてサイクルを2つ作ると、これらの少なくとも一方の総和は$\ 0\ $でないので矛盾する。また、この距離を$\ d\ $とすると、$\ T\ $から$\ S\ $へのパスの距離は$\ -d\ $となっているはずである。そして逆に任意の2点についてこの距離の条件が成り立てば、条件を満たすことがわかる。これは簡単なdfsで求めることができる。計算量は$\ O(n)\ $である(これは嘘で、本当は$\ O(n + m) = O(m) = O(n^2)\ $)。
さて、次に長さを変える辺を一つ固定し、この繋いでいる頂点を$\ S \ $ -> $\ T\ $、その長さを$\ x\ $と仮定する。すると、まず必要条件として、上の条件と同じようなことを考えることができるはずであり、あるサイクルに関して総和が$\ 0\ $になるようにするための$\ x\ $が一意に定まるはずである。これを決めてしまえば、上で述べた判定によって、全体が条件を満たすかを判定すればよい。
従って、これをすべての辺についてチェックすればよい。この操作は$\ O(m)\ $でできるので、全体の計算量は$\ O(nm)\ $となるので十分高速である(これは嘘で、本当は$\ O(n^4)\ $なので、間に合わない)。
よって間に合わないことがわかるが、せっかく書いたので通したい。辺を順番に見ている場合でもほとんど通っているので、辺をランダムにシャッフルし、前半の$\ X\ $%だけ試す方針をとることにする。これはstd::shuffle
を使うなどして実装する。辺が少ないときはすべて見てよいとし、辺数が$\ 50000\ $を超えるときは$\ 60\ $%、辺数が$\ 75000\ $を超えるときは$\ 35\ $%をみて、条件を満たすものが見つかるまで探索し、なければ即終了するという実装にすると、5回投げたら3回通るくらいの精度(?)になった。
実装
#include "bits/stdc++.h" using namespace std; #define mp make_pair static const long long INFL = 0x3f3f3f3f3f3f3f3fLL; long long dist[303]; vector<pair<int, long long>> g[303]; struct edge { int a, b; long long cost; }; void init() { for (int i = 0; i < 303; i ++) dist[i] = -INFL; } bool check(int v, int from, int to, long long x, long long d) { dist[v] = d; for (auto e : g[v]) { int u = e.first, cost = e.second; if (from == v && to == u) { assert(d == 0); if (!check(u, from, to, x, d + x)) return false; } else if (dist[u] == -INFL) { if (!check(u, from, to, x, d + cost)) return false; } else if (dist[u] != d + cost) { return false; } } return true; } long long get_x(int v, long long d) { dist[v] = d; for (auto e : g[v]) { int u =e.first, cost = e.second; if (dist[u] == -INFL) { long long x = get_x(u, d + cost); if (x != INFL) return x; } else if (dist[u] != d + cost) { return d + cost - dist[u]; } } return INFL; } int main() { int n, m; scanf("%d%d", &n, &m); vector<edge> es; for (int i = 0; i < m; i ++) { int a, b; long long c; scanf("%d%d%lld", &a, &b, &c); a --, b --; g[a].push_back(mp(b, c)); es.push_back((edge) {a, b, c} ); } //Randomly choose the edges random_device seed_gen; mt19937 engine(seed_gen()); shuffle(es.begin(), es.end(), engine); for (int i = 0; i < m; i ++) { if (m > 50000) if (i > (m * 60) / 100) break; if (m > 75000) if (i > (m * 35) / 100) break; edge e = es[i]; int v = e.a, u = e.b; long long cost = e.cost; init(); dist[v] = 0; long long x = get_x(u, 0); if (x == INFL) x = 0; init(); bool ans = check(v, v, u, -x, 0); if (ans) return !puts("Yes"); } return !puts("No"); }