SRM 150 Division-I, Level 2

Problem Statement
   
Karel is a frustrated painter who works by day in an electrical repair shop. Inspired by the color-coded bands on resistors, he is painting a series of long, narrow canvases with bold colored stripes. However, Karel is lazy and wants to minimize the number of brush strokes it takes to paint each canvas.
Abbreviating each color to a single uppercase letter, Karel would write the stripe pattern red-green-blue-green-red as "RGBGR" (quotes added for clarity). It would take him three brush strokes to paint this pattern. The first stroke would cover the entire canvas in red (RRRRR). The second stroke would leave a band of red on either side and fill in the rest with green (RGGGR). The final brush stroke would fill in the blue stripe in the center (RGBGR).
Given a stripe pattern stripes as a string, calculate the minimum number of brush strokes required to paint that pattern.
Definition
   
Class:
StripePainter
Method:
minStrokes
Parameters:
string
Returns:
int
Method signature:
int minStrokes(string stripes)
(be sure your method is public)
   

Notes
-
The blank canvas is an ugly color and must not show through.
Constraints
-
stripes will contain only uppercase letters ('A'-'Z', inclusive).
-
stripes will contain between 1 and 50 characters, inclusive.
Examples
0)

   
"RGBGR"
Returns: 3
Example from introduction.
1)

   
"RGRG"
Returns: 3
This example cannot be done in two strokes, even though there are only two colors. Suppose you tried to paint both red stripes in one stroke, followed by both green stripes in one stroke. Then the green stroke would cover up the second red stripe. If you tried to paint both green stripes first, followed the red stripes, then the red stroke would cover up the first green stripe.
2)

   
"ABACADA"
Returns: 4
One long stroke in color 'A', followed by one stroke each in colors 'B', 'C', and 'D'.
3)

   
"AABBCCDDCCBBAABBCCDD"
Returns: 7

4)

   
"BECBBDDEEBABDCADEAAEABCACBDBEECDEDEACACCBEDABEDADD"
Returns: 26

This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.

Implementation

This was an unusually hard 500p, requiring a non-straightforward dynamic programming solution (or memoization). As almost always is the case with this type of problems, the solution is easy once you see it. However, in an ordinary SRM, this would be rated as a hard problem. Why it was not a 600 pointer, I don't understand.

Lets try the standard induction approach: given a continuous part of the original strip and the current color this strip has (initially the whole strip has an undefined color), we want to break this down into smaller instances of the same problem. Define this problem like this:

min[left,size,color] = the minimum number of brushes required to paint position
                       left, left+1, ... , left+size-1 of the original strip,
                       assuming these position currently has color color.

The desired return value is then simply min[0,stripes.length(),'?'].

It requires one insight to solve this min-problem: the next stroke may always start at the leftmost position unless this position is already in the correct color. If the leftmost position is in the wrong color, we must paint it sooner or later anyway, so why not now? This reasoning only make sense for the leftmost (and rightmost) position, because there is no gain to first paint this position with another color, which may be true of interior positions.

So we first check if the leftmost position is in the wrong color. If it already is in the desired color, we simply return min[left+1,size-1,color] since we then have one less position to worry about. Otherwise we loop over all possible stroke lengths, from 1 to size:

loop i from 1 to size
   sum = 1 + min[left+1,i-1,stripe[left]] + min[left+i,size-i,color];
   update best solution found so far if necessary
end loop

The logic is as follows: we make a stroke of color stripe[left] (the color of the leftmost position) from left to left+i-1, inclusive - this accounts for the 1. We then get two subproblems, one part of the strip is in color stripe[left], the other part is in the original color. We solve these subproblems by a recursive call. On the part of the strip which we changed color, we know that the leftmost position is in the correct color, so we can skip ahead one position there (thus explaining the left+1,i-1).

What remains is a terminating case, so we don't recurse forever. That one is simple: if the length of the stripe is zero, it requires no strokes at all.

When implementing this, it's essential that we use memoization, otherwise our solution will time out pretty fast. Memoization should always be considered in a recursive function which doesn't have any side effects, such as changing global variables. A function is a pure mathematical function if it always returns the same value given the same input parameters. Such functions are the only ones memoization can be applied on: if we ever call the function with the same parameters as we've done before, we just look up what answer we got last time instead of evaluating it again. It can be implemented like this:

int memo[50][51][128]; // Initialize to -1 to mark not evaluated

int min(int left, int size, char col)
{
  if (memo[left][size][col]>=0) return memo[left][size][col];
 
  // Evaluate function and store result in best
 
  memo[left][size][col]=best;
  return best;
}

One can also use dynamic programming to solve the problem. This is basically the same thing, except that instead of the recursive calls, we evaluate the memo table in such an order that the results of recursive calls already has been calculated, like this:

for(int size=0;size<=stripes.length();size++)
  for(left=0;left<stripes.length()-size;left++)
    for(col='@';col<='Z';col++) {
      // Evaluate min[left][size][col] here
    }

Notice that in the evaluation of min[left,size,col] the subproblems always had a smaller size than the original problem, so if we evaluate the subproblems starting from size 0 and then working up, these recursive calls can instead be looked up directly from the memo table as we know they have already been evaluated.

And that's it! The hardest part in a problem like this is figuring out the mathematical function and what parameters it should use. Once this is done, it's pretty straightforward. Just remember that the function must be a mathematical one (without side effects), otherwise we can't use memoization.

你可能感兴趣的:(visio)