import java.io.*; import java.util.*; import java.awt.*; import java.text.*; import java.math.*; /** * Solution to Museum Guards * * We'll frame this as a Max Flow problem, but with a twist. We'll start with a brief * discussion of Max Flow problems. * * Consider a directional, weighted graph that has a special node called the 'Source', * and another called the 'Sink'. Consider the weights to be a 'capacity'. Think of * each edge like a one-way pipe, and the 'capacity' is the most stuff you can push through that pipe. * (The internet is a series of tubes, right?) Well, the question is... How much stuff can you * push from the Source to the Sink, given the limited capacities of all the edges? * * The solution technique for a Max Flow problem is called Ford-Fulkerson, and I'll only * give a brief outline of it here. * * First, for every edge A->B, create a dual edge, B->A, which starts with 0 capacity. * The way to think of A->B and its capacity is: How much capacity is available to use. * The way to think of B->A and its capacity is: How much capacity has been used, that I could give back if I had to. * That's why B->A starts out as 0 - because, at the beginning of the algorithm, you haven't done anything, * so you haven't used any capacity. * * Now, find a path from the Source to the Sink, only traversing edges that have some capacity (that is, capacity>0), * and don't hit any node more than once. You can find this path any way you like, but Breadth-First Search * has become the standard. This program will use BFS, but DFS or any other method is fine. * * When you've got a path, go back over the edges on the path, and find the one with the smallest * capacity. That's the most stuff you can push through that path, right? That's the weakest link. * Then, go back over the path again, and move that amount of capacity from each edge to its dual * (that is, subtract it from each edge, add it to each edge's dual). * * Keep doing this (finding paths, finding the min capacity, moving that capacity from each edge * to its dual) until you can't find a path with >0 capacity. Then, you're done! The total amount * of capacity that you moved over all the paths you found is the Max Flow! * * OK, so how do we see Museum Guards as a Max Flow problem? * Well, the 'stuff' we'll flow through the system is guard-periods. Build two kinds of nodes, * for guards, and for time periods. Link the Source to each guard, with the capacity being the * number of time periods that guard is able to work. Link a guard to a time period if the guard * is willing to work that time period, with capacity 1. Finally, link the time periods to the sink. * What should the capacity for those edges be? Well, that's tough. We could set them all to n, * and see how many guard-periods flow through - but we have no way of forcing the * guard-periods to be balanced over the time periods. Some periods could be over-represented, * and some under-represented. The result wouldn't be meaningful. Hmmm.... * * Consider this: If we set the capacity of each of those links to some value x, 0<=x<=n, * and we get a total flow of x*48 (48=number of 30 minute periods in a day), * then we know we can cover all time periods with x guards. The only way to get * a total flow of x*48 is to get a full x from each of the 48 time periods. We can't get * any more from one time period (and less from another), because we've set the * max capacity from each time period to the sink to x. That's our hook. * We just have to do repeated Ford-Fulkersons to find the largest value of x which leads to success. */ class guards { public Scanner sc; public PrintStream ps; // Program was written with different periods-per-hour possible. // We settled on 2 periods per hour (by half-hours), but this code // could have handled other cases. public static final int PERIODS_PER_HOUR = 2; public static final int PERIODS_PER_DAY = 24*PERIODS_PER_HOUR; public static final int MINUTES_PER_PERIOD = 60/PERIODS_PER_HOUR; public static final int MINUTES_PER_DAY = 24*60; public String toString() { return "guards"; } /** * A Node in the Max Flow graph */ public class Node { // A list of edges public LinkedList edges; // Whether or not this node has been visited by the current search public boolean visited; // A name, for debugging purposes only. public String name; /** * Create a node. * * @param n The Name of the node */ public Node( String n ) { name = n; edges = new LinkedList(); visited = false; } /** * Standard toString() method, to help with debugging. * * @return A pretty string version of the node (which is just the name). */ public String toString() { return name; } } /** * An Edge in the Max Flow graph. */ public class Edge { // Each edge has a capacity, public int cap; // A destination, public Node dest; // And a "dual" edge public Edge dual; /** * Create an edge. * * @param d The destination of this edge. */ public Edge( Node d ) { cap = 0; dest = d; dual = null; } } /** * A State in the BFS. */ public class State { // The node where we are public Node node; // The edge that got us here public Edge edge; // A link to the previous state public State prevstate; /** * Create a State. * * @param n The Node * @param e The Edge that got us here * @param p The previous State */ public State( Node n, Edge e, State p ) { node = n; edge = e; prevstate = p; } } /** * Create a link (and its dual) between nodes A and B. * * @param a Node A * @param b Node B * @param capacity Capacity from A to B (it'll be 0 from B to A) */ public void link( Node a, Node b, int capacity ) { // Create the edge from A to B, and its dual from B to A. Edge a2b = new Edge( b ); Edge b2a = new Edge( a ); // Add them to their respective Nodes a.edges.add( a2b ); b.edges.add( b2a ); // They are duals of each other a2b.dual = b2a; b2a.dual = a2b; // Set their capacities a2b.cap = capacity; b2a.cap = 0; } /** * Parse a string of the form HH:MM and return the number * of minutes represented. * * @param time A String time, like 02:34 * @return The number of minutes represented by the String time */ public int getMinutes( String time ) { String tokens[] = time.split( ":" ); int h = Integer.parseInt( tokens[0] ); int m = Integer.parseInt( tokens[1] ); return h*60 + m; } /** * Convert minutes to periods * * @param minutes Minutes * @return Periods */ public int minutesToPeriods( int minutes ) { return minutes * PERIODS_PER_HOUR / 60; } // The nodes - source, sink, guards, time periods. public Node source, sink, guards[], periods[]; // We'll reuse this same queue for all of our BFS's. public LinkedList queue = new LinkedList(); /** * Can we make a schedule such that every time period is covered by x guards? * * @param x A number of guards to check * @return true if we can cover every time period with x guards, otherwise false. */ public boolean cando( int x ) { // Start by resetting the system. First, source to guards for( Edge e : source.edges ) { // We never get rid of capacity, we just move it from the edge to its dual. // So here, we'll just move it back. e.cap = e.cap+e.dual.cap; e.dual.cap = 0; } // Now, guards to periods for( Node g : guards ) { for( Edge e : g.edges ) { // Guards have 2 kinds of edges - those to time periods, // and those back to the source that are duals of edges from // the source to the guard. We only want to reset the ones to time // periods - we already reset the duals back at the source. if( e.dest != source ) { e.cap = 1; e.dual.cap = 0; } } } // Finally, periods to sink for( Node p : periods ) { for( Edge e : p.edges ) { // Periods have 2 kinds of edges - to the sink, and duals // back to the guards. here, we're only interested in those // that go to the sink. if( e.dest == sink ) { e.cap = x; e.dual.cap = 0; } } } // Total capacity int totalcap = 0; // Now, go through Ford-Fulkerson for(;;) { // Mark all of the nodes as unvisited - this is a fresh BFS. // Source is the exception, since we're marking nodes when we put them // on the queue rather than when we're taking them off. source.visited = true; for( Node g : guards ) { g.visited = false; } for( Node p : periods ) { p.visited = false; } sink.visited = false; // Start the BFS. Clear the queue, add the source queue.clear(); queue.add( new State( source, null, null ) ); // We want to remember the final state we ended up in. // That's so we can do the part of Ford-Fulkerson where // we trace our route, find the smallest capacity along the route, // and use that capacity along the route. State finalstate = null; // And, off we go with a BFS. while( queue.size()>0 ) { // Grab the next state State s = queue.removeFirst(); // Did we reach the sink? Then we're done if( s.node == sink ) { finalstate = s; break; } // Otherwise, go through this node's edges for( Edge e : s.node.edges ) { // Look for edges that have capacity>0, that go // to nodes we haven't visited yet if( e.cap>0 && !e.dest.visited ) { // Mark'em, and queue'em e.dest.visited = true; queue.add( new State( e.dest, e, s ) ); } } } // If we never reached the sink, then we're done. if( finalstate==null ) break; // Figure out how much capacity this route used. // This is a "weakest link" thing - you can't push through any more // capacity than the smallest pipe will allow. // So, we go back through all of the edges we used, to // find the smallest capacity. int capused = Integer.MAX_VALUE; for( State s=finalstate; s.edge!=null; s=s.prevstate ) { if( s.edge.cap