310 likes | 374 Views
David Van Brackle Chief Judge, Southeast USA Region, ICPC. 2012 Problem Set Review. Problems. Candy Store. 1D Dynamic Programming on Pennies b est[ i ] is the most calories you can buy with i pennies m axcal = 0 For i = 0 to max dollars by penny For each type of candy j
E N D
David Van Brackle Chief Judge, Southeast USA Region, ICPC 2012 Problem Set Review
Candy Store • 1D Dynamic Programming on Pennies • best[i] is the most calories you can buy with i pennies maxcal = 0 For i = 0 to max dollars by penny For each type of candy j totalcost = i + cost[j] totalcal = best[i] + cals[j] If totalcost<=max and totalcal > best[totalcost] best[totalcost] = totalcal If totalcal > maxcal maxcal = totalcal End If End If End For End For • One thing to look out for – the max calories may not occur at the max cost!
Collision Detection • Have to simulate this one • Some useful physics formulas: Let the inputs per car be [t0,x0,y0,v0] and [t1,x1,y1,v1] Acceleration: a=(v1-v0)/(t1-t0) Velocity: v(t)=a*(t-t1) + v1 Angle of movement: Θ=atan2( y1-y0, x1-x0) Acceleration x-y: ax=a*cos(Θ), ay=a*sin(Θ) Velocity x-y: vx1=v1*cos(Θ), vy1=v1*sin(Θ) Position accel: pax(t)=½*ax*(t-t1)2 + vx1*(t-t1) pay(t)=½*ay*(t-t1)2 + vy1*(t-t1) Terminal Velocity: If a<0, vT=0 If a>0, vT = 80 vxT=vT*cos(Θ), vyT=vT*sin(Θ) Time of freeze: solve v(t)=vT for t tT=(vT + a*t1 – v1) / a [if a=0, tT=+infinity] Position of freeze: xT=pax(tT), yT=pay(tT) Position: px(t) = If t<tT then pax(t) else xT+vxT*(t-tT) py(t) = If t<tT then pay(t) else yT+vyT*(t-tT) Now, just iterate from the max time to the max time + 30 seconds by some small time increment. Compute positions and distance, and see if they’re ever closer than 18.
Component Testing • Envision as a max flow graph: Every Component linked to Sink. Weight = # of tests that component needs Engineers Components Source Sink Source linked to every engineer. Weight = # of components that engineer can test Every Engineer linked to every Component. Weight = 1 Of course, since there can be 109 engineers and 109 components, this is too big for Ford/Fulkerson
Component Testing • But Max Flow is the same as Min Cut. Look at it as a Min Cut problem. Engineers Components Source Sink Start by putting all of the SourceEngineer edges in the cut. The cost of this cut is the sum of all jobs for all engineers.
Component Testing • Now, try to take a SourceEngineer edge out of the cut and put it back in the graph. Engineers Components Source Sink Then, we’ve got to add to the cut all of the edges from that node to all of the Components in the graph. The change to the value of the cut is: old value - [jobs for that engineer] + [# of Components NOT in the cut]
Component Testing • Try to put a ComponentSink edge into the cut. Engineers Components Source Sink Then, we can take out of the cut all of the edges from Engineers not in the cut to that Component. The change in the value of the cut is: old value + [Tasks for that Component] - [# of Engineers NOT in the cut]
Component Testing • So, here’s our strategy: • Sort both the Engineers and Components by numbers of tasks • Remove engineers from the cut from largest to smallest • For each removed engineer, add Components from smallest to largest • Only add components if there’s an improvement: • [Tasks for that Component] < [# of Engineers NOT in the cut] • If min cut = sum of all component needs, then we’ve got a “Yes”. • If min cut < that sum, the answer is “No”
Component Testing • Note that once [# of Engineers NOT in the cut] is bigger than any [Tasks for that Component] , then all components are in the cut. That’s a complete cut, and there’s no need to go any further. • But, [Tasks for that Component] is capped at 100,000, so there’s never any need to go any further than 100,000 engineers. • We remove components by comparing [Tasks for that Component] to [# of Engineers NOT in the cut]. But, [Tasks for that Component] is the same for every component in a class. So, we don’t have to go through the components one-by-one, we can remove them in bulk. • Note that Engineers are independent of Components – that is, the effect of manipulating an Engineer is dependent only on the number of Components in/out of the cut, NOT on which ones.
Component Testing long eout = 0; long cout = totalcomps; long mincut = Math.min(totalgots,totalneeds); long cut = totalgots; int c = 0; for( int j=m-1; j>=0 && c<n; j-- ) { for( int k=0; k<jobtitles[j].count && c<n; k++ ) { ++eout; cut -= jobtitles[j].amount; cut += cout; while( c<n && comptypes[c].amount<=eout ) { cut += comptypes[c].count * comptypes[c].amount; cut -= comptypes[c].count * eout; cout -= comptypes[c].count; ++c; } if( cut<mincut ) mincut = cut; } } ps.println( (mincut==totalneeds) ? 1 : 0 );
Do It Wrong,Get It Right • So, we want to find a and m such that a/m - b/n = (a-b)/(m-n) • Do some algebra on the above equation, and you can come up with this: • (an-bm)/nm = (a-b)/(m-n) • (m-n)(an-bm) = nm(a-b) • anm - an2 - bm2 + bnm = anm - bnm • an2 = 2bmn - bm2 • a = (2bmn - bm2)/n2 • Now, let m=kn for some k • a = [2b(kn)n - b(kn)2]/n2 • a = [2bkn2 - bk2n2]/n2 • a = 2bk - bk2
Do It Wrong,Get It Right • The problem is that k doesn't have to be an integer. But, we can limit it. • k has to be a rational number - that is, a fraction. • Since m=kn and m is an integer, then k's denominator must be a factor of n. • Now, look at a = 2bk - bk2. • That's a quadratic in k, with roots at 0 and 2 • When k=0, obviously a=0. When k=2, a = 4b-4b = 0. when k=1, a = 2b-b = b. • So, since a must be non-negative, 0<=k<=2. • That means that k's numerator must be between 0 and 2*denominator. • So, now we have a strategy for looping through k’s potential denominator, and for looping through k’s potential numerator based on its denominator. • Loop through them, build a and m and see if they work • If they do, put them in a set to sort later • There are other things you can do to make it more efficient
Dueling Philosophers • For every essay, keep track of needs and feeds: • needs[i] is the number of essays which must come before this one • feeds[i] is a list of essays that need this one to come before them • On input d and u, ++needs[u], add u to feeds[d]. • Keep a list ‘zeros’ of all essays with 0 needs • These can be scheduled right away answer = 1 For 1 to the number of essays If zeros.size==0, then we have a cycle. answer = 0, exit loop. If zeros.size>1, then we’ve got multiple solutions. answer=2, (but we can’t exit the loop because we might find a cycle later) Take an essay d off of the zeros list. For every essay u in feeds[d], --needs[u]. If needs[u]==0, add u to zeros
Funhouse • Build the Room polygons • For each point, keep track of the edges. Sort them by angle. • Trace around a room edge-by-edge. At every point, take the next edge in the sorted list from the one you came in on • If you reach the end of the list, go back to the beginning • Go in both directions • Compute area of each room • Area = ½ * | Sum((x2-x1)*(y2+y1)) |
Funhouse • Build a Max Flow graph for Ford/Fulkerson • Create 2 nodes for each room: ‘Enter’ and ‘Exit’ • Create edges: • For each room, create edge EnterExit with weight equal to the area of the room • For each room with an Entrance, create an edge SourceEnter with weight MAX • For each room with an Exit, create an edge ExitSink with weight MAX • For any two rooms with a door between them create two edges: Exit1Enter2 and Exit2Enter1, each with weight MAX. • Note: For every edge AB you create, you’ll also create a back edge BA with weight 0.
A Terribly Grimm Problem • Backtracking, with a lot of pruning. • The number of consecutive composites is surprisingly small • For the limits of this problem (1010), the largest “prime gap” is 282 • Any prime larger than 282 can only appear once in the sequence • That limits the number of primes for which you have to keep track of whether they’re used or not • For every number in the sequence, build up a list of candidate primes • Also build a separate count of primes • Recurse allocating primes to numbers in the sequence • Decrement the prime count of every other number with that prime as a candidate • Allocate to numbers with 1 remaining possibility first • If any number has 0 remaining possibilities, fail right there.
Heads or Tails • Figure out how many cells have 1 neighbor, 2, 3, 4, 5 • “Neighbor” means a cell which can flip it if pressed, including the cell itself • Compute the probability of any cell being a Head after k random presses, if it starts out a head: • Let prob[i] be that probability for a cell with i neighbors. • Start with prob[i] = 1 • For j=1 to k: prob[i] = prob[i]*((ncoins-i)/ncoins) + (1.0-prob[i])*(i/ncoins) • Next, figure out which configurations yield an expectednumber of headsbetween a and b for( int i1=0; i1<=counts[1]; i1++ ) for( int i2=0; i2<=counts[2]; i2++ ) for( int i3=0; i3<=counts[3]; i3++ ) for( int i4=0; i4<=counts[4]; i4++ ) for( int i5=0; i5<=counts[5]; i5++ ) { double expected = i1*prob[1] + (counts[1]-i1)*(1.0-prob[1]); expected += i2*prob[2] + (counts[2]-i2)*(1.0-prob[2]); expected += i3*prob[3] + (counts[3]-i3)*(1.0-prob[3]); expected += i4*prob[4] + (counts[4]-i4)*(1.0-prob[4]); expected += i5*prob[5] + (counts[5]-i5)*(1.0-prob[5]); if( a<=expected && expected<=b ) configs.add( { i1, i2, i3, i4, i5 } ); }
Heads or Tails • Now, what should the board look like? • Set base = 0 • Start at the top left corner, and iterate through every spot • Start by assuming it is a head • Figure out how many satisfactory configurations follow that prefix • If configs >=i, then keep going. • If configs <i, then there aren’t enough good configurations when this cell is a Head. So, it has to be a Tail. Set base = base + configs
Heads or Tails • How do we know how many satisfactory board positions follow? • Well, we know how many heads of each type we’ve already seen in the prefix • For each still viable configuration, we know how many heads we need. • Then, it’s just: • Sum for each still viable configuration of • The product for each type of cell of • (counts[t]-prefix.length) C (config[t]-used[t]) • counts[t]-prefix.length is the number of cells of this type that are yet to be filled • config[i]-used[t] is the number of heads we have left • C is statistical “Choose” – nCm is the number of ways of choosing m things out of a group of n, where order is not important. • nCm = n!/(m!(n-m)!) (though there are much more efficient ways of computing it)
Paint Me • Needed= 2*width*height + 2*length*height + length*width • Subtract off every w*h for all windows and doors • Cans = Needed / area • If Needed % area != 0 then add one to Cans • Biggest problem teams had: 0 windows/doors
Party Games • Seems simple – but it can be tricky! • Let’s just look at the middle 2 names: • A, B A • AA, B AA • AAA, B AB • AZZQ, B AZZQ • AZZQX, B AZZR • FRANK, FRAO FRANL • The best solution is to build substrings (S) of the first string (a) in order of length 1, 2, 3… etc, wherethe last character of the substring is incremented by 1 • Exceptions: If the last character is Z, or if the substring is the original string • Stop when you find a string S where a<=S<b • For example, if a=EZEKIEL, build strings: • F • EZ • EZF • EZEL • EZEKJ • EZEKIF • EZEKIEL
Reverse Nonogram • Just array manipulation. • If you’re really clever, you can reuse code for rows/columns, rather than risking cut-n-paste errors. • Be careful of spaces at the end!
Tsunami • Minimum Spanning Tree for a directed graph • Or, regular MST, with an added restriction. • Start with Kruskal, which goes like this: • Start with each node in its own set. • If you've got n nodes, then you've got n sets. • Then, sort all of edges by weight (in this case, length or distance). • Go through the edges, smallest to largest. • If an edge connects two nodes that are in difference sets, then union those two sets, and add that edge to the Minimum Spanning Tree. • We’ve got to deviate from Kruskal, because of our political restriction.
Tsunami • Suppose the edge looks like this (a’s y > b’s y): • Think about all of the nodes that are in a's set. They're all already connected with edges - that's why they're in the same set. • If any one of them has a smaller y than a, then we risk sending the warning up to a, and then back down again to that lower city. We can't do that. • So, if a has the higher y of the edge, then a's y must be the minimum y of all of the nodes in a's set. • Ditto for b - if it has the larger y, then that y value must be the minimum y of all of the cities in b's set. • If a.y == b.y, then all we require is that ONE of them be the minimum of their set. a b
Tsunami • Maintaining the sets can be done in a clever way: as a tree. • Create an array parent[i], where parent[i] is the parent of node I, or -1 if I is the root of its subtree. • Use the index of the root as a unique identifier for the set. • To merge nodes i and j, just set parent[i]=j • There’s also some trickery you can do here to make things more efficient. You can go up the tree, setting parents as you go, to “squash” the tree, reducing its depth. • This technique will also allow you to same the minimum y value in the set. Just create a miny[i] array. At the start, miny[i] = the y of point I (since every point is its own set). • When you merge I with j, find the root of I’s subtree & j’s subtree, aand set the new root’s miny to be the minimum of the minys of the two roots.
Unhappy Numbers • We’ll compute the count of unhappy numbers from 0 to n. • If we need from a to b, then we’ll print unhappy(b) – unhappy(a-1) • A number is unhappy iff the sum of squares of its digits is unhappy. • For all of the numbers in our range, the max sum of squared digits is less than 2000 • I believe it’s 1522, for 8999999999999999999 • So, we can compute unhappiness for the first 2000 numbers by brute force, put the results into a boolean array, and for every other number, just sum digit squares and index into that boolean array. • But, we can’t do that 1018 times. We need a shortcut.
Unhappy Numbers • Strategy: For n, build an array counts[], where counts[i] is a count of the numbers from 0 to n with a squared-digit sum of i. • Then, total all of those counts[i] where unhappy[i] is true • That will give a count of the unhappy numbers from 0 to n
Unhappy Numbers • Now to compute that array. • This is a complicated procedure, so we will frame it with a specific example: x=6724. • First, Figure out the highest digit, and its power of 10. • For our example (6724), digit=6, power=1000. • Break down the problem by that first digit. • In our example (6724), that means calculating the digit sums for • 0-999 • then 1000-1999 • then 2000-2999 • then 3000-3999 • then 4000-4999 • then 5000-5999 • and finally, 6000-6724.
Unhappy Numbers • First, get the digit sums for all 9s • In our example, that’s 999. • subsums[] = sumcounts( power-1 ); • You can memoize these, since we’ll be using them over and over • Now, go through the first digits • In our example, that’s 0-5 • for( int d=0; d<digit; d++ ) • for( int i=0; i<sums.length-d*d; i++ ) • sums[i+d*d] += subsums[i]; • Now, handle the biggest digit • In our example, that’s 6. x % power = 6724 % 1000 = 724 • subsums[] = sumcounts( x % power ); • for( int i=0; i<sums.length-digit*digit; i++ ) • sums[i+digit*digit] += subsums[i];
Walls • You can’t go through all possible wall positions, there are just too many. • However, you CAN go through all possible wall positions in one dimension, and then use a simple sweep to see where the walls need to be in the other dimension!