from copy import copy, deepcopy import sys from itertools import combinations as combs from fractions import Fraction s = set() # this holds the 0-based times s1 = set() # this holds the intermediate-based times ''' timelist contains the list of event times for the path we're on fuselist contains the state of the fuses Rules for optimization: 1. Left ends of fuses get lit before or at the same time as the right end. 2. Don't light the left end of a fuse unless all previous fuses have their left ends lit. 3. Don't light the right end of a fuse if it will create a fuse with the same state as a previous fuse. ''' def burn(timelist, fuselist_in): fuselist = deepcopy(fuselist_in) for i in xrange(len(fuselist)-1, -1, -1): if fuselist[i][2] == 0: fuselist.pop(i) # Find first unlit left end. # This will be the first candidate for lighting. for l in xrange(len(fuselist)): if not fuselist[l][0]: break else: l = len(fuselist) # l is index of first unlit left end. # Build up possible new left-end lightings. # These can be none, the first unlit, the first two unlit, the first three unlit, ... leftset = [[]] for i in xrange(l, len(fuselist)): new = copy(leftset[-1]) new.append(i) leftset.append(new) # Process all possible combinations of left-end and right-end lightings. for left in leftset: # Create possible right-end lightings given the current set of left-end lightings. # If the left-end set is empty, then the right-end set is limited to fuses that already have the left end lit. # Otherwise, the right-end set may include fuses that are now having their left ends lit. if left == []: rightmax = l else: rightmax = left[-1]+1 rightset = [] # Apply rules for i in xrange(rightmax): # Make sure right end is not already lit and the fuse isn't burned out. if not fuselist[i][1] and fuselist[i][2] > 0: # Apply rule 3 - don't allow duplicate states for fuses after right end is lit skip = False for j in xrange(i): if fuselist[j] == fuselist[i]: skip = True break if not skip: rightset.append(i) if left == [] and rightset == []: # If there are any ends lit, this must be processed for f in fuselist: if (f[0] or f[1]) and f[2] > 0: break else: continue # Light the selected ends using all combinations of valid right ends for x in xrange(0, len(rightset)+1): for right in combs(rightset, x): fuses = deepcopy(fuselist) times = deepcopy(timelist) # Mark the ends as lit. for l in left: fuses[l][0] = True for r in right: fuses[r][1] = True # Calculate next event time. elapsed = 2**20 for f in fuses: if f[0]: if f[1]: rem = f[2]/2 if rem > 0: elapsed = min(elapsed, rem) else: rem = f[2] if rem > 0: elapsed = min(elapsed, rem) # Make the intervening time elapse. if False: for f in fuses: if f[2] > 0: if f[0] and not f[1]: f[2] -= elapsed elif f[0] and f[1]: f[2] -= 2*elapsed else: for f in xrange(len(fuses)-1, -1, -1): if fuses[f][2] > 0: if fuses[f][0] and not fuses[f][1]: fuses[f][2] -= elapsed elif fuses[f][0] and fuses[f][1]: fuses[f][2] -= 2*elapsed if fuses[f][2] == 0: fuses.pop(f) else: fuses.pop(f) times.append(times[-1] + elapsed) # Update the solution sets (0-based and intermediate-based) s.add(times[-1]) for i in xrange(len(times)-1): s1.add(times[-1]-times[i]) # If any fuse has some time left, recurse. if len(fuses) > 0 and max([f[2] for f in fuses]) > 0: burn(times, fuses) if len(sys.argv) != 2: print 'need number of ropes' sys.exit(1) num = int(sys.argv[1]) if num < 1: print 'bad number of ropes' sys.exit(1) fuselist = [[False, False, 2**num] for i in xrange(num)] burn([0], fuselist) s1 = sorted(list(s1-s)) s = sorted(list(s)) for x in xrange(len(s)): s[x] = Fraction(s[x], 2**num) for x in xrange(len(s1)): s1[x] = Fraction(s1[x], 2**num) print 'zero-based solutions:' for x in s: print '%d/%d ' % (x.numerator, x.denominator), print print 'intermediate-based solutions:' for x in s1: print '%d/%d ' % (x.numerator, x.denominator), print print len(s), '+', len(s1), '=', len(s)+len(s1)