import java.io.PrintStream; import java.util.Arrays; import java.util.Comparator; import java.util.PriorityQueue; import java.util.Scanner; /** * Solution to Unbalanced Parentheses * * Here's a fun fact: In any string of parentheses that is unbalanced, * there is always some point in the string where all of the unmatched * parens to the left are close parens, and all to the right are open parens. * * We'll use this fact. We'll try to find the right split point. We'll make all * of the open parens to the left candidates to be flipped to closes, and all close parens to the * right candidates to be flipped to opens. We'll find the best of the candidates to flip to create * all of the mismatches we need. * * @author vanb */ public class unbalanced_vanb { private static Scanner sc; private static PrintStream ps; /** This will be the best (lowest) cost we've found so far. */ private int bestcost = Integer.MAX_VALUE; /** This will be a running total of the costs of the candidates to be flipped. */ private int bestsofar = 0; /** Turn debugging on (true) or off (false). */ private static boolean debugging = true; /** * Print a debugging message if debugging is on. * * @param msg Debugging message */ private static void debug( String msg ) { if( debugging ) System.err.println( msg ); } /** * This class will hold the important info about one parenthesis in the string. * * @author vanb */ private class Parenthesis implements Comparable { /** Open or close? */ public char ch; /** Cost for flipping this paren */ public int cost; /** Is this one that we've chosen to flip? */ public boolean flip; /** * Create a Parenthesis. * * @param ch The character * @param cost The cost of flipping this one */ public Parenthesis( char ch, int cost ) { this.ch = ch; this.cost = cost; this.flip = false; } @Override /** * Compare two parentheses by cost. * */ public int compareTo( Parenthesis other ) { return this.cost - other.cost; } /** * Create a pretty String for debugging. * * @return A pretty String representation of this Parenthesis. */ public String toString() { return "[ch=" + ch + " cost=" + cost + " flip=" + flip + "]"; } } /** * These two PriorityQueues together hold all of our candidates. * As the number of flips we need changes, we'll shuttle Parentheses * between the two. */ /** * This holds all of the Candidates that we;ve chosen to flip. * It's sorted high-to-low, so the first to come off are the * ones with the worst cost. */ private PriorityQueue flips; /** * This holds all of the candidates NOT chosen to be flipped. * It's sorted low-to-high, so the first ones off are the * ones with the best cost. */ private PriorityQueue others; /** * Adjust the PriorityQueues based on the number of flips we need. * * @param need The number of flips we need */ private void adjust( int need ) { // If we have more flips than we need, // move the worst ones out of 'flips' and into 'others' while( flips.size()>need && flips.size()>0 ) { Parenthesis other = flips.poll(); other.flip = false; others.add( other ); bestsofar -= other.cost; } // If we have fewer flips than we need, // take the best ones from 'others' while( flips.size()0 ) { Parenthesis other = others.poll(); other.flip = true; flips.add( other ); bestsofar += other.cost; } // If we have exactly what we need, and our // cost is better than any we've seen so far, // remember it. if( flips.size()==need && bestsofar( n, Comparator.reverseOrder() ); others = new PriorityQueue( n ); // OK, read in the paren string, create the Parenthesis array. char parens[] = sc.next().toCharArray(); Parenthesis parentheses[] = new Parenthesis[n]; // Some parens will have negative cost. // We will always flip these. We'll call them 'freebies'. int freebies = 0; // Figure out the number of unmatched open parens, and unmatched close parens // already in the string. int openunmatched = 0; int closeunmatched = 0; for( int i=0; i0 ) --openunmatched; else ++closeunmatched; } // Look at this string: "))" // There are 2 unmatched parens, but it only takes one flip to fix it: "()" // Likewise, "))((" has 4 unmatched, but it only takes 2 flips to fix it: "()()" // So, every 2 unmatched parens takes 1 flip to fix. // So, how many flips to we need to make sure it takes Bruce more than k flips to fix it? // It takes k+1, minus the credit we get for unmatched parens already in the string. int need = k - (openunmatched+closeunmatched)/2 + 1; // If there are already enough mismatches in the string (need<=0), // then Barry doesn't need to do anything. bestcost=0 // If the string is of odd length, it's impossible to balance. // Barry doesn't need to do anything. bestcost=0 if( need<=0 || (n&1)==1 ) bestcost = 0; else { // OK, we've got some work to do. bestsofar = 0; // We're going to look for that magic split point, between all mismatched closes and // all mismatched opens. Every close paren to the right of that split point // will be a candidate for flipping. Likewise, all of the opens to the left. // We'll start with the split point at the very start of the string, so all // close parens will be candidates. // This will be the difference between opens and closes (positive if more opens) to the right of // the split point. Why start at 1? Because we're going to divide by 2 to get the number // of flips we don't have to do because of a mismatch, and we want 1->1, 2->1, 3->3, 4->2, etc. int endbalance = 1; for( Parenthesis p : parentheses ) { if( p.ch==')' ) { --endbalance; add( p ); } else ++endbalance; } // Adjust our structures and get the best cost. // Why shift by 1? well, if endbalance is positive, it's the same // as dividing by 2. If endbalance is negative, it's a bit different: // -1->-1, -2->-1. -3->-2, -4->-2, and that's what we want. // And, endbalance can be negative. It means we have more closes than // we want, so we have to do extra work adjust( k - (endbalance>>1) + 1 ); // OK, now try other split points. // startbalance is the same kind of thing as endbalance: it's the // balance to the left of the split point instead of the right. // Also, it'll be positive if there are more closes than opens - // that's what we want to the left. int startbalance = 1; for( Parenthesis p : parentheses ) { if( p.ch=='(' ) { // Losing an open is bad for endbalance. // Gaining an open is bad for startbalance. --startbalance; --endbalance; // Opens are candidates for flipping // when they're to the left of the split point. add(p); } else { // If it's not an open, then it must be a close. // Losing a close is good for endbalance. // Gaining a close is good for startbalance. ++startbalance; ++endbalance; // Closes are no longer candidates for flipping // when they're to the left of the split point. remove(p); } // Adjust, and see if we found a better solution. adjust( k - (endbalance>>1) - (startbalance>>1) + 1 ); } } // bestcost starts out as maxint. // If we never find anything better, then we never find a solution. // Otherwise, it's the bestcost minus the freebies (which, you'll remember, // is the sum of the costs of all of the negatives we flipped at the beginning). ps.println( bestcost==Integer.MAX_VALUE ? "?" : ("" + (bestcost - freebies))); } /** * Main! * * @param args Command-line args, unused * @throws Exception */ public static void main( String[] args ) throws Exception { sc = new Scanner( System.in ); ps = System.out; new unbalanced_vanb().doit(); } }