/******************************************************************/ /* ACM ICPC 2014-2015 */ /* Northeastern European Regional Contest */ /* St Petersburg - Barnaul - Tbilisi - Tashkent, December 7, 2014 */ /******************************************************************/ /* Problem C. Cactus Generator */ /* */ /* Original idea Roman Elizarov */ /* Problem statement Roman Elizarov */ /* Test set Roman Elizarov */ /******************************************************************/ /* Checker */ /* */ /* Author Roman Elizarov */ /******************************************************************/ import ru.ifmo.testlib.Checker; import ru.ifmo.testlib.InStream; import ru.ifmo.testlib.Outcome; import java.util.*; import static ru.ifmo.testlib.Outcome.Type.*; /** * Checker for NEERC'2014 Problem C: Cactus Generator * * @author Roman Elizarov */ public class Check implements Checker { public static final int MAX_N = 50000; static class Graph { final Outcome.Type type; int n; int m; List> adjSet = new ArrayList<>(); List> adj = new ArrayList<>(); Graph(InStream in, Outcome.Type type) { this.type = type; n = in.nextInt(); if (n < 1 || n > MAX_N) throw new Outcome(type, String.format("n=%d exceeds limits", n)); adjSet.add(null); adj.add(null); for (int i = 1; i <= n; i++) { adjSet.add(new LinkedHashSet<>()); adj.add(new ArrayList<>()); } m = in.nextInt(); if (m < 1) throw new Outcome(type, String.format("Invalid m=%d", m)); for (int i = 1; i <= m; i++) { int k = in.nextInt(); if (k < 2) throw new Outcome(type, String.format("Invalid k[%d]=%d", i, k)); int u = nextV(in); for (int j = 2; j <= k; j++) { int v = nextV(in); if (u == v) throw new Outcome(type, String.format("Invalid edge %d -- %d", u, v)); add(u, v); add(v, u); u = v; } } if (!in.seekEoF()) throw new Outcome(type, "Extra data in file"); } private int nextV(InStream in) { int v = in.nextInt(); if (v < 1 || v > n) throw new Outcome(type, String.format("Invalid vertex number %d", v)); return v; } private void add(int u, int v) { if (!adjSet.get(u).add(v)) throw new Outcome(type, String.format("Edge %d -- %d repeats", u, v)); adj.get(u).add(v); } } static class Vertex implements Comparable { final int index; // original index final Map next = new HashMap<>(); // cycles handing at this vertex (keyed by the next vertex) int num; // number in dfs order int nextAdjIdx; // next adj vertex to be scanned Vertex vUp; // uplink in dfs boolean hasLastVertex; // when tree down from here contains last vertex (index == n) Cycle[] ordered; // ordered cycles Vertex(int index) { this.index = index; } @Override public String toString() { return "" + index; } int size() { return next.size(); } void initOrder() { for (Cycle cycle : next.values()) { cycle.initOrder(); hasLastVertex |= cycle.hasLastVertex; } ordered = next.values().toArray(new Cycle[size()]); Arrays.sort(ordered); } @Override public int compareTo(Vertex o) { int d = cmpLastVertex(hasLastVertex, o.hasLastVertex); if (d != 0) return d; d = o.size() - size(); if (d != 0) return d; for (int i = 0; i < size(); i++) { d = o.ordered[i].compareTo(ordered[i]); if (d != 0) return d; } return 0; } } static class Cycle implements Comparable { final List v = new ArrayList<>(); // list of vertices in a cycle, v(0) is up, v(1) is its key in up's next map boolean hasLastVertex; // when tree down from here contains last vertex (index == n) Vertex[] ordered; // ordered vertices @Override public String toString() { return v.toString(); } int size() { return v.size(); } int cmp(int i, int j) { return ordered[i].compareTo(ordered[j]); } void initOrder() { for (int i = 1; i < size(); i++) { hasLastVertex |= v.get(i).hasLastVertex; } ordered = v.toArray(new Vertex[size()]); int d = 0; for (int i = 1; i < (size() + 1) / 2; i++) { d = cmp(i, size() - i); if (d != 0) break; } if (d < 0) // then flip around for (int i = 1; i < (size() + 1) / 2; i++) { Vertex t = ordered[i]; ordered[i] = ordered[size() - i]; ordered[size() - i] = t; } } @Override public int compareTo(Cycle o) { int d = cmpLastVertex(hasLastVertex, o.hasLastVertex); if (d != 0) return d; d = o.size() - size(); if (d != 0) return d; for (int i = 1; i < size(); i++) { d = o.ordered[i].compareTo(ordered[i]); if (d != 0) return d; } return 0; } } static int cmpLastVertex(boolean hasLastVertex, boolean other) { if (hasLastVertex && !other) return 1; if (!hasLastVertex && other) return -1; return 0; } class DFS { final Graph g; final Outcome.Type type; final Vertex[] v; // all vertices final Stack st = new Stack<>(); // dfs stack of vertices final Vertex v1; int curNum; // current dfs vertex number DFS(Graph g, Outcome.Type type) { this.g = g; this.type = type; v = new Vertex[g.n + 1]; for (int i = 1; i <= g.n; i++) v[i] = new Vertex(i); // mark a distinguished last vertex v[g.n].hasLastVertex = true; // start dfs at vertex 1 (first) v1 = v[1]; pushV(v1); while (!st.isEmpty()) { Vertex s = st.peek(); List sAdj = g.adj.get(s.index); int i = s.nextAdjIdx++; if (i >= sAdj.size()) { // done with it -- backtrack s.initOrder(); st.pop(); continue; } Vertex t = v[sAdj.get(i)]; visit(s, t); } if (curNum != g.n) throw new Outcome(type, "Graph is not connected"); } // add vertex to stack void pushV(Vertex t) { t.num = ++curNum; st.push(t); } // visits edge from s to t in dfs Cycle visit(Vertex s, Vertex t) { if (t.num > 0) { if (s.num < t.num) return null; // already processed if (s.vUp == t) return null; // one back -- skip (cycle code will not change anything) // cycle Cycle c = new Cycle(); Vertex j = s; while (j != t) { c.v.add(j); j = j.vUp; } c.v.add(t); Collections.reverse(c.v); // now from t to s for (int i = 1; i < c.v.size(); i++) { Vertex p = c.v.get(i - 1); Vertex q = c.v.get(i); Cycle oldC = p.next.remove(q); if (oldC.v.size() != 2) throw new Outcome(type, String.format("Not a cactus. Edge %s -- %s in more than one loop", s, t)); } t.next.put(c.v.get(1), c); return c; } // push fresh vertex onto stack pushV(t); t.vUp = s; // create simple cycle (two vertices) Cycle c = new Cycle(); c.v.add(s); c.v.add(t); s.next.put(t, c); return c; } } public Outcome test(InStream inf, InStream ouf, InStream ans) { Graph ja = new Graph(ans, FAIL); Graph pa = new Graph(ouf, PE); if (ja.n != pa.n) return new Outcome(WA, String.format("Expected %d vertices, found %d", ja.n, pa.n)); if (ja.m < pa.m) return new Outcome(WA, String.format("Expected %d paths, found %d", ja.m, pa.m)); // Now need deep stack Comparer comparer = new Comparer(ja, pa); comparer.start(); try { comparer.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } if (comparer.result != 0) throw new Outcome(WA, "Different graph"); if (ja.m > pa.m) return new Outcome(FAIL, String.format("Expected %d paths, found %d with the same graph!", ja.m, pa.m)); return new Outcome(OK, String.format("%d %d", pa.n, pa.m)); } private class Comparer extends Thread { private final Graph ja; private final Graph pa; int result; public Comparer(Graph ja, Graph pa) { this.ja = ja; this.pa = pa; } @Override public void run() { Vertex jv1 = new DFS(ja, FAIL).v1; Vertex pv1 = new DFS(pa, WA).v1; result = jv1.compareTo(pv1); } } }