// Solution attempt at NAIPC 2019 // Trains, Planes, and No Automobiles // // @author godmar import java.util.*; import java.util.stream.*; import java.io.*; import static java.lang.Math.*; public class TrainsPlanesNoAutos_godmar { @SuppressWarnings("unchecked") public static void main(String ...av) { Scanner s = new Scanner(); int n = s.nextInt(); int m = s.nextInt(); // first, compute minimum path cover AhujaOrlin isap = new AhujaOrlin(); Node src = isap.addNode(); Node snk = isap.addNode(); Node [] left = new Node[n]; Node [] right = new Node[n]; for (int i = 0; i < n; i++) { left[i] = isap.addNode(); right[i] = isap.addNode(); isap.link(src, left[i], 1); isap.link(right[i], snk, 1); } for (int i = 0; i < m; i++) { int a = s.nextInt() - 1; int b = s.nextInt() - 1; isap.link(left[a], right[b], 1); } long matched = isap.getMaxFlow(src, snk); long unmatched = n - matched; System.out.println(unmatched - 1); if (unmatched != 1) { // We have found a maximum flow, corresponding to maximum matching // we now want to see how it can be rearranged to a different, but // still maximum matching. This is like trying to find an augmenting // path that adds zero flow. // // Any node we visit in the residual graph on the left-hand side is // either unmatched to begin with, or could be unmatched after we pushed // flow back from the right to the left on some path. Deque bfs = new ArrayDeque<>(); bfs.offer(src); boolean [] lvisited = new boolean[isap.nodes.size()]; while (!bfs.isEmpty()) { Node u = bfs.poll(); for (Edge e : u.edges) { if (e.remaining() > 0) { if (!lvisited[e.to.index]) { lvisited[e.to.index] = true; bfs.offer(e.to); } } } } // now copy/paste the same from the right symmetrically - try to undo matchings. // any right side node we hit was either unmatched already (when we come // from the sink) or could be unmatched by rerouting flow to the left and then // right. This is like sucking flow towards the sink. bfs = new ArrayDeque<>(); bfs.offer(snk); boolean [] rvisited = new boolean[isap.nodes.size()]; while (!bfs.isEmpty()) { Node u = bfs.poll(); for (Edge e : u.edges) { if (e.dual.remaining() > 0) { if (!rvisited[e.to.index]) { rvisited[e.to.index] = true; bfs.offer(e.to); } } } } StringBuilder output = new StringBuilder(); String sep = ""; for (int u = 0; u < n; u++) { if (lvisited[left[u].index] || rvisited[right[u].index]) { output.append(sep + (u+1)); sep = " "; } } System.out.println(output.toString()); } } //-----------Scanner class for faster input---------- /* Provides similar API as java.util.Scanner but does not * use regular expression engine. */ public static class Scanner { BufferedReader br; StringTokenizer st; public Scanner(Reader in) { br = new BufferedReader(in); } public Scanner() { this(new InputStreamReader(System.in)); } String next() { while (st == null || !st.hasMoreElements()) { try { st = new StringTokenizer(br.readLine()); } catch (IOException e) { e.printStackTrace(); } } return st.nextToken(); } int nextInt() { return Integer.parseInt(next()); } long nextLong() { return Long.parseLong(next()); } double nextDouble() { return Double.parseDouble(next()); } // Slightly different from java.util.Scanner.nextLine(), // which returns any remaining characters in current line, // if any. String readNextLine() { String str = ""; try { str = br.readLine(); } catch (IOException e) { e.printStackTrace(); } return str; } int[] readIntArray(int n) { int[] a = new int[n]; for (int idx = 0; idx < n; idx++) { a[idx] = nextInt(); } return a; } long[] readLongArray(int n) { long[] a = new long[n]; for (int idx = 0; idx < n; idx++) { a[idx] = nextLong(); } return a; } } //-------------------------------------------------------- //- begin canned maxflow ISAP code public static class Node { // thou shall not create nodes except through addNode() private Node() { } List edges = new ArrayList(); int index; // index in nodes array int dist; int currentarc; } public static class Edge { boolean forward; // true: edge is in original graph // false: edge is a backward edge Node from, to; // nodes connected long flow; // current flow final long capacity; Edge dual; // reference to this edge's dual // thou shall not create edges except through link() protected Edge(Node s, Node d, long c, boolean f) { forward = f; from = s; to = d; capacity = c; } // remaining capacity() long remaining() { return capacity - flow; } // increase flow and adjust dual void addFlow(long amount) { flow += amount; dual.flow -= amount; } } /* A Max Flow solver base class. */ public static abstract class MaxFlowSolver { /* List of nodes, indexed. */ List nodes = new ArrayList(); /* Create an edge between nodes n1 and n2 */ public void link(Node n1, Node n2, long capacity) { Edge e12 = new Edge(n1, n2, capacity, true); Edge e21 = new Edge(n2, n1, 0, false); e12.dual = e21; e21.dual = e12; n1.edges.add(e12); n2.edges.add(e21); } /* Create an edge between nodes n1 and n2 */ void link(int n1, int n2, long capacity) { link(nodes.get(n1), nodes.get(n2), capacity); } /* Create a graph with n nodes. */ protected MaxFlowSolver(int n) { for (int i = 0; i < n; i++) addNode(); } protected MaxFlowSolver() { this(0); } public abstract long getMaxFlow(Node src, Node snk); /* Add a new node. */ public Node addNode() { Node n = new Node(); n.index = nodes.size(); nodes.add(n); return n; } /* Compute the distance labels (.dist) of all nodes that * can reach the sink in the actual graph (not residual graph) * via backwards BFS. * Assumes that v.dist == -1 for all nodes prior to calling this * function. Also populates an array 'count' that yields * the number of nodes with a given distance label. */ void computeDistanceLabelsByReverseBFS(Node snk, int [] count) { final int n = nodes.size(); count[0]++; Node [] Q = new Node[n]; // (Q, head, tail) are used as BFS queue int head = 0, tail = 0; snk.dist = 0; Q[tail++] = snk; while (head < tail) { Node x = Q[head++]; for (Edge e : x.edges) { if (e.to.dist == -1) { // not yet explored e.to.dist = e.from.dist + 1; count[e.to.dist]++; Q[tail++] = e.to; } } } } } /** * Implements Ahuja/Orlin. * * Ahuja/Orlin describe this as an optimized variant of the original Edmonds-Karp * shortest augmenting path algorithm. * This algorithm is also referred to as "ISAP" by some: * 'Improved Shortest Augmenting Path' algorithm. * * Ahuja, R. K. and Orlin, J. B. (1991), Distance-directed augmenting path algorithms * for maximum flow and parametric maximum flow problems. * Naval Research Logistics, 38: 413–430. * doi:10.1002/1520-6750(199106)38:3<413::AID-NAV3220380310>3.0.CO;2-J * and parametric maximum flow problems. * This is algorithm DD1 in the paper. */ static class AhujaOrlin extends MaxFlowSolver { /* Create a graph with n nodes. */ AhujaOrlin () { this(0); } AhujaOrlin (int n) { super(n); } @Override public long getMaxFlow(Node src, Node snk) { final int n = nodes.size(); long totalFlow = 0; for (Node u : nodes) { u.dist = -1; u.currentarc = 0; // current active arc } // count is used to implement the "gap rule" that lets us bail as soon as // maxflow is found int [] count = new int[n+1]; // count[i] number of nodes u with u.dist == i // INITIALIZE. Perform a (reverse) breadth-first search of the residual network // starting from the sink node to compute distance labels d(i). computeDistanceLabelsByReverseBFS(snk, count); if (src.dist == -1) // no flow if source is not reachable return 0; ArrayDeque augpath = new ArrayDeque<>(); Node u = src; // u is the 'tip' of the augmenting path as in paper while (src.dist < n) { // If d(s) >= n, then STOP. // Find if current node u has an admissible edge towards sink Edge admissible = null; for (; u.currentarc < u.edges.size(); u.currentarc++) { Edge e = u.edges.get(u.currentarc); // an admissible edge (i, j) is one for which d(i) = d(j) + 1 if (e.remaining() > 0 && u.dist == e.to.dist + 1) { admissible = e; break; } } if (admissible != null) { Node v = admissible.to; //pred[j.index] = admissible; augpath.push(admissible); if (v == snk) { // AUGMENT path using maximum possible flow based on bottleneck long addedFlow = Long.MAX_VALUE; for (Edge edge : augpath) addedFlow = min(addedFlow, edge.remaining()); while (!augpath.isEmpty()) augpath.pop().addFlow(addedFlow); totalFlow += addedFlow; u = src; // start over at source } else { u = v; // ADVANCE } } else { // RETREAT // check if maximum flow was already reached. This is indicated // if there are no nodes at a given distance (a 'gap') if (--count[u.dist] == 0 && u.dist < src.dist) break; // Side note: nodes at i.dist describe a min cut int mindj = n; // relabel based on child with smallest dist for (Edge e : u.edges) if (e.remaining() > 0) mindj = min(mindj, e.to.dist + 1); u.dist = mindj; count[u.dist]++; u.currentarc = 0; // reset current arc on node u if (u != src) // retreat u = augpath.pop().from; } } return totalFlow; } } }