Datacenter Cooling (or Counting Hamiltonian Paths in a Grid)
// Datacenter Cooling (or Counting Hamiltonian Paths in a Grid) // // Written by Grant Jenks // http://www.grantjenks.com/ // // DISCLAIMER // THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE // LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR // OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, // EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE // ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. // SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY // SERVICING, REPAIR OR CORRECTION. // // Copyright 2012 // This work is licensed under the // Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License // To view a copy of this license, visit // http://creativecommons.org/licenses/by-nc-nd/3.0/ // or send a letter to // Creative Commons // 171 Second Street, Suite 300 // San Francisco, California, 94105, USA // // I originally saw this problem at http://www.quora.com/challenges, but it // looks like an ACM competition / AI textbook problem. // // Build with: // cl.exe /EHsc /O2 [/GL] [/DMEASURETIME] datacenter.cpp // // Datacenter Cooling // We have some rooms in our datacenter, and we need to connect them all with // a single cooling duct. // Here are the rules: // * The datacenter is represented by a 2D grid. // * Rooms we own are represented by a 0. // * Rooms we do not own are represented by a 1. // * The duct has to start at the air intake valve, which is represented // by a 2. // * The duct has to end at the air conditioner, which is represented // by a 3. // * The duct cannot go in multiple directions out of the intake or the // AC - they must be the two endpoints of the duct. // * The duct must pass through each room exactly once. // * The duct cannot pass through rooms we do not own. // * The duct can connect between rooms horizontally or vertically but // not diagonally. // Here is an example datacenter: // 2 0 0 0 // 0 0 0 0 // 0 0 3 1 // There are two possible ways to run the duct here: // 2--0--0--0 // | // 0--0--0--0 // | // 0--0--3 1 // or // 2 0--0--0 // | | | // 0 0 0--0 // | | | // 0--0 3 1 // Write a program to compute the number of possible ways to run the duct. For // the above example, the correct answer is 2. // // Input format: // Your program should read from stdin. The input will be a series of // integers separated by whitespace. // The first two integers will be W and H, with width and height of the // datacenter. // These will be followed by W*H more integers, specifying the 2D grid // representing the datacenter. // // Output format: // Your program should write a single integer out to stdout: the number of // possible ways to run the duct. // // Test Inputs // Small (result: 2) // 4 3 // 2 0 0 0 // 0 0 0 0 // 0 0 3 1 // Medium (result: 20) // 5 4 // 2 0 0 0 0 // 0 0 0 0 0 // 0 0 0 0 0 // 0 0 0 0 3 // Benchmarking (result: 301716) // 7 8 // 2 0 0 0 0 0 0 // 0 0 0 0 0 0 0 // 0 0 0 0 0 0 0 // 0 0 0 0 0 0 0 // 0 0 0 0 0 0 0 // 0 0 0 0 0 0 0 // 0 0 0 0 0 0 0 // 3 0 0 0 0 1 1 // // Changelog // 2012-03-15 Grant Jenks // * Did a little research online and learned a new trick. If, while computing // reachability (the inner-dfs loop), a dead-end is discovered and the // dead-end is not the end position, then there are no possible paths. This // is a cheaper and narrower test for the observation in 2010-07-24. // Processing time: ~2.2 seconds // // 2012-03-14 Grant Jenks // * Realized that I could memoize the tape. What if you occupy the same rooms // in two different configurations? Wouldn't the result be the same after // that point? For example: // If the starting board is: // S 0 0 0 0 // 0 0 0 0 0 // 0 0 0 0 0 // 0 0 0 0 0 // 0 0 0 0 E // And we reach this point (numbers indicate traversal pattern): // S 1 8 0 0 // 3 2 7 0 0 // 4 5 6 0 0 // 0 0 0 0 0 // 0 0 0 0 E // Then we will compute how many paths are reachable. // And if later we reach this point: // S 7 8 0 0 // 1 6 5 0 0 // 2 3 4 0 0 // 0 0 0 0 0 // 0 0 0 0 E // Then we know already (from memoization of the results) that this will // yield the same number of paths. // I prototyped these changes in a Python version of this program. While // the memoized results were frequently hit, cache maintenance and size // were impractical. // // 2010-07-24 Grant Jenks // * Came up with an idea to approximate this problem as a max-flow problem. // This led me to consider what would happen if there existed an open // room, r, not adjacent to the end room, e, such that closing the room // would make the DFS test fail. Before this is done, I think it would // be good to add more comments and refactor the code some. // I got distracted from completing the above and didn't come back to the // code until a different idea come to me two years later. // // 2010-04-03 Grant Jenks // * Converted conditionals in loop to straight line code. // Processing time: ~40 seconds // // 2010-03-27 Grant Jenks // * Added BOUNDARY to datacenter array to eliminate bounds checking. // Processing time: ~60 seconds // // 2010-03-20 Grant Jenks // * Added post-inner-DFS loop check that all open rooms were reachable. // Processing time: ~2 minutes // // 2010-03-13 Grant Jenks // * Added the inner DFS loop but only checked that end was reachable. // Processing time: ~2 hours // // 2010-03-06 Grant Jenks // * Created the basic outer loop with tape. // Processing time: Several hours. #define OPEN 1 #define CLOSED 0 #define VISITED 0 #define BOUNDARY 0 #define DFS_VISIT 8 #define VISIT_MASK 0x80000000 // For data input/output #include <iostream> #include <iterator> // For measuring time accurately #ifdef MEASURETIME #include <windows.h> #endif MEASURETIME // To coerce the compiler to use certain forms #include <intrin.h> // Unsigned int sizes typedef unsigned __int8 __uint8; typedef unsigned __int16 __uint16; typedef unsigned __int32 __uint32; using namespace std; int main() { #ifdef MEASURETIME LARGE_INTEGER start_time; QueryPerformanceCounter(&start_time); #endif __uint32 o_width, o_height, a_width, a_height; __uint32 datacenter_size, start, end, not_owned; // Use __uint16 though the backing array will be __uint8 so that input is not // interpreted as characters. typedef istream_iterator<__uint16> iis; // Read in the initial width and height and initialize the datacenter array. iis input = iis(std::cin); o_width = *input++; o_height = *input++; a_width = o_width + 2; a_height = o_height + 2; datacenter_size = a_width * a_height; __uint8 * datacenter = new __uint8[datacenter_size]; // The perimeter of the datacenter is denoted by the BOUNDARY. This allows us // to skip bounds checking when looking at adjacent rooms to visit. for (__uint32 pos = 0; pos < a_width; pos++) { datacenter[pos] = BOUNDARY; datacenter[pos + (a_width * (a_height - 1))] = BOUNDARY; } for (__uint32 pos = 0; pos < a_height; pos++) { datacenter[pos * a_width] = BOUNDARY; datacenter[(pos * a_width) + (a_width - 1)] = BOUNDARY; } // Read in the rest of the datacenter. start = 0; end = 0; not_owned = 0; for(__uint32 o_pos = 0; input != iis(); input++, o_pos++) { __uint8 value = *input; __uint32 o_pos_w = o_pos % o_width; __uint32 o_pos_h = o_pos / o_width; __uint32 pos = (o_pos_w + 1) + ((o_pos_h + 1) * a_width); switch (value) { case 0: datacenter[pos] = OPEN; break; case 1: not_owned++; datacenter[pos] = CLOSED; break; case 2: if (start != 0) { cerr<<"Start defined twice!"<<endl; return 1; } start = pos; datacenter[pos] = OPEN; break; case 3: if (end != 0) { cerr<<"End defined twice!"<<endl; return 2; } end = pos; datacenter[pos] = OPEN; break; default: cerr<<"Invalid input: "<<value<<endl; return 3; } } // Define the tape. // Note that we're playing a game with the visited bit by putting // it in the high-bit of the tape position. The positions stored // on the tape are most likely less than 2^31-1 so this won't be // an issue. __uint32 * tape = new __uint32[4 * datacenter_size]; __uint32 * tape_pos = tape; __uint32 * dfs = new __uint32[datacenter_size + 1]; // Because we've recorded the 'start' and 'end' positions, we can // open the rooms. datacenter[start] = OPEN; datacenter[end] = OPEN; *tape_pos = 0x7FFFFFFF; // Rather than recursing, the tape is used to solve the problem // iteratively. This gives finer-grain control over the assembly. tape_pos++; *tape_pos = start; __uint32 steps = 0; __uint32 routes = 0; __uint32 max_steps = o_width * o_height - not_owned; while (tape_pos != tape) { __uint32 pos = *tape_pos; if (pos == end) { steps++; *tape_pos |= VISIT_MASK; if (steps == max_steps) { routes++; } } else { __uint32 * stack_pos = tape_pos; __uint32 * dfs_pos = dfs; int visited_count = 0; // Do a DFS through the open rooms to make sure all are // reachable and none (but the end room) are a dead-end. while (stack_pos >= tape_pos) { __uint32 cur = *stack_pos; stack_pos--; if (datacenter[cur] != OPEN) continue; visited_count++; datacenter[cur] = DFS_VISIT; dfs_pos++; *dfs_pos = cur; __uint32 above = cur - a_width; __uint32 left = cur - 1; __uint32 right = cur + 1; __uint32 below = cur + a_width; __uint8 value, sum = 0; stack_pos++; // A trick is being played here with 'value &= 1' so that // the following is straight-line code. (Conditional branches // will cause hiccups in the processor.) Since only open // rooms have value 1, they alone will be stored on the stack. // This avoids visiting closed or visited rooms. value = datacenter[above]; sum += value; value &= 1; *stack_pos = above; stack_pos += value; value = datacenter[right]; sum += value; value &= 1; *stack_pos = right; stack_pos += value; value = datacenter[below]; sum += value; value &= 1; *stack_pos = below; stack_pos += value; value = datacenter[left]; sum += value; value &= 1; *stack_pos = left; stack_pos += value; if (sum == DFS_VISIT && cur != end) { // If the sum of the values of the surrounding rooms // is DFS_VISIT then it must be that all but one are // closed - e.g. with p denoting the current position: // 0 0 0 // 0 p 8 // 0 0 0 // This is a dead-end. Unless this position is also // end, there will be no path through here that connects // start and end. // Set visited_count to max_steps only so the // reachability check will fail. visited_count = max_steps; break; } stack_pos--; } // Re-open those rooms which were marked DFS_VISIT within the // above loop. for(; dfs_pos > dfs; dfs_pos--) { datacenter[*dfs_pos] = OPEN; } *tape_pos = (pos | VISIT_MASK); datacenter[pos] = VISITED; // This is the reachability check. We will only append new // positions to visit on the tape if if ((visited_count + steps) == max_steps) { __uint32 above = pos - a_width; __uint32 left = pos - 1; __uint32 right = pos + 1; __uint32 below = pos + a_width; __uint8 value; tape_pos++; // This is using the same trick as the DFS loop for // reachability. There's no need to 'value &= 1' // because every room has either a 1 or 0 in it. If // we add a new position to the tape it is initially // added un-visited. We will mark it visited later // with VISIT_MASK. value = datacenter[above]; *tape_pos = above; tape_pos += value; value = datacenter[right]; *tape_pos = right; tape_pos += value; value = datacenter[below]; *tape_pos = below; tape_pos += value; value = datacenter[left]; *tape_pos = left; tape_pos += value; tape_pos--; } steps++; } // This is where the tape is used to rewind rooms occupied in the // datacenter. Above, on either sides of the if/else, was: // *tape_pos = (pos | VISIT_MASK); // which set the high-bit in the tape position. Now, _bittestandreset, // returns that bit and sets it to 0. So the pseudocode reads: // while the high bit at tape_pos is set // clear the high bit // mark the room at tape_pos as OPEN // decrement tape_pos and steps while(_bittestandreset((long *)tape_pos, 31)) { datacenter[*tape_pos] = OPEN; tape_pos--; steps--; } } cout<<routes<<endl; #ifdef MEASURETIME LARGE_INTEGER stop_time; QueryPerformanceCounter(&stop_time); LARGE_INTEGER frequency_time; QueryPerformanceFrequency(&frequency_time); double seconds = (double)(stop_time.QuadPart - start_time.QuadPart) / (double)(frequency_time.QuadPart); cout<<"Computed in "<<seconds<<" seconds."<<endl; #endif MEASURETIME }