/* * O((nm) ^ (3/2)) solution to abstract art. * * The problem is to find a minimum vertex cover with an additional * problem of determining which nodes you can include in the dual * independent set while still maintaining the minimum vertex cover. * * The graph is bipartite, so the minimum vertex cover can be * obtained via a maximum matching. The second half of the problem * can be solved by using the constructive proof of Konig's theorem. * * --- I use the notation from the Wikipedia proof --- * Run the constructive proof of Konig's theorem. We can observe that * no vertex in set R and in the vertex cover can be added to the * independent set for this matching. Perhaps for another matching? * * It turns out the right hand side independent set remains invariant * across all matchings. To prove this examine any alternating cycle * (alternate edges between match and unmatch) in the matching graph. * Augmenting along this cycle produces another matching with the same * indepedent set within R. All matchings can be obtained via some * number of augmenting cycles, proving the invariant. * * So the solution becomes, run the construction from Konig's theorem * and find independent set I_1. Run the construction again flipping * L and R sets, find the independent set I_2. The answer is the * independent set I_1 union I_2 (print the revelant colors instead). */ import java.util.*; import java.io.*; public class AbstractArt_font { public static void main(String[] args) throws Exception { PrintWriter out = new PrintWriter(System.out); new AbstractArt_font(new FastScanner(System.in), out); out.close(); } int N, M; char[][] grid, backup; int[] dr = {-1,0,1,0}; int[] dc = {0,-1,0,1}; int minVertexCover; int getNode(int i, int j) { return i * M + j; } ArrayList[] adj; void addEdge(int i, int j, boolean isMatched) { adj[i].add(new Edge(i,j,isMatched)); adj[j].add(new Edge(j,i,isMatched)); } // Run flow to find the vertex cover. int matchOppositeColors() { ISAP flowSolver = new ISAP(N*M); for (int i=0; i(); int minVertexCover = flowSolver.run(); // Save the matching for (int i=0; i q = new ArrayDeque<>(); boolean[] side = new boolean[N*M]; for (int i=0; i 0) { int x = q.poll(); if (side[x]) { // Traverse unmatched edges for (Edge e : adj[x]) { if (!Z[e.j] && !e.matchedEdge) { q.add(e.j); Z[e.j] = true; } } } else { // Traverse matched edges for (Edge e : adj[x]) { if (!Z[e.j] && e.matchedEdge) { q.add(e.j); Z[e.j] = true; } } } } boolean[] inVertexCover = new boolean[N*M]; for (int x=0; x canKeep = new TreeSet<>(); for (int i=0; i[] adj; ArrayDeque q; ArrayDeque path; int[] level; int[] curEdge; int[] count; public ISAP(int numNodes) { this.source = numNodes++; this.sink = numNodes++; this.numNodes = numNodes; adj = new ArrayList[numNodes]; for (int i=0; i(); } q = new ArrayDeque<>(); path = new ArrayDeque<>(); level = new int[numNodes]; curEdge = new int[numNodes]; count = new int[numNodes+1]; } public void addEdge(int i, int j, int cap) { Edge fwd = new Edge(i, j, cap); Edge rev = new Edge(j, i, 0); fwd.rev = rev; rev.rev = fwd; adj[i].add(fwd); adj[j].add(rev); } void bfs() { Arrays.fill(level, numNodes); level[sink] = 0; q.clear(); q.add(sink); while (!q.isEmpty()) { int i = q.poll(); for (Edge e : adj[i]) { if (e.rev.cap > 0 && level[e.j] == numNodes) { level[e.j] = level[i] + 1; q.add(e.j); } } } } public int run() { bfs(); for (int i=0; i 0) { admissibleEdge = e; break; } curEdge[i]++; } if (admissibleEdge == null) { // Relabel & Retreat count[level[i]]--; if (count[level[i]] == 0) break; level[i] = numNodes; for (Edge e : adj[i]) if (e.cap > 0) level[i] = Math.min(level[i], 1+level[e.j]); count[level[i]]++; curEdge[i] = 0; if (i != source) { Edge e = path.pop(); i = e.i; } } else { path.push(admissibleEdge); i = admissibleEdge.j; if (i == sink) { int bottleneck = Integer.MAX_VALUE; for (Edge e : path) bottleneck = Math.min(bottleneck, e.cap); totalFlow += bottleneck; Edge firstZero = null; for (Edge e : path) { e.cap -= bottleneck; e.rev.cap += bottleneck; if (firstZero == null && e.cap == 0) firstZero = e; } while (!path.isEmpty()) { Edge e = path.pop(); i = e.i; if (e == firstZero) break; } if (level[source] != last) { last = level[source]; } } } } return totalFlow; } class Edge { int i, j, cap; Edge rev; public Edge(int i, int j, int cap) { this.i = i; this.j = j; this.cap = cap; this.rev = null; } } } class FastScanner { private InputStream stream; private byte[] buf = new byte[1024]; private int curChar; private int numChars; public FastScanner(InputStream stream) { this.stream = stream; } int read() { if (numChars == -1) throw new InputMismatchException(); if (curChar >= numChars) { curChar = 0; try { numChars = stream.read(buf); } catch (IOException e) { throw new InputMismatchException(); } if (numChars <= 0) return -1; } return buf[curChar++]; } boolean isSpaceChar(int c) { return c==' '||c=='\n'||c=='\r'||c=='\t'||c==-1; } boolean isEndline(int c) { return c=='\n'||c=='\r'||c==-1; } int nextInt() { return Integer.parseInt(next()); } long nextLong() { return Long.parseLong(next()); } double nextDouble() { return Double.parseDouble(next()); } String next() { int c = read(); while (isSpaceChar(c)) c = read(); StringBuilder res = new StringBuilder(); do { res.appendCodePoint(c); c = read(); } while(!isSpaceChar(c)); return res.toString(); } String nextLine() { int c = read(); while (isEndline(c)) c = read(); StringBuilder res = new StringBuilder(); do { res.appendCodePoint(c); c = read(); } while(!isEndline(c)); return res.toString(); } }