SRM 161 Division-I, Level 2


Problem Statement
   
You are practicing your tennis game with a hitting partner. Each time the ball comes over the net a player can either hit it cross-court, or down-the-line. In this problem, a sequence of shots will be denoted by a string composed of (quotes for clarity) the letters 'c' and 'd', representing cross-court and down-the-line shots respectively. For example, "cccddc" would be such a sequence consisting of 3 cross-courts, 2 down-the-lines, and a final cross-court.  Since you are going to practice a particular play strategy there are certain shot sequences you will avoid. You are given a string[] forbidden containing precisely which sequences must be avoided. For example, if (quotes for clarity) "ccd" is an element of forbidden then you should never allow 2 cross-court shots followed by a down-the-line shot to occur consecutively at any point in the rally. If you were a professional, a single forbidden sequence might cause you to stop hitting. Since you are an amateur, you are willing to let allowed distinct forbidden sequences to occur before you stop. For example, if allowed was 2, the second forbidden sequence would terminate the hitting sequence.  You will return the number of distinct hitting sequences of length numLength which contain fewer than allowed forbidden sequences. Two hitting sequences are distinct if they differ at some stroke in the sequence. Two forbidden sequences are distinct if they differ in length, or position in the hitting sequence they begin at. For example, if forbidden = {"cc","cd","ccd"} then the sequence "ccccdd" has 5 distinct forbidden sequences (3 distinct "cc" sequences, a "cd" sequence, and a "ccd" sequence).
Definition
   
Class:
TennisRallies
Method:
howMany
Parameters:
int, string[], int
Returns:
int
Method signature:
int howMany(int numLength, string[] forbidden, int allowed)
(be sure your method is public)
   

Constraints
-
numLength will be between 1 and 18 inclusive
-
forbidden will contain between 0 and 10 elements inclusive
-
allowed will be between 1 and 100 inclusive
-
forbidden will contain no repeated elements
-
Each element of forbidden will contain between 1 and 18 'c's and 'd's inclusive (quotes for clarity; both are lowercase)
Examples
0)

   
3
{"cc","dd"}
1
Returns: 2
Since allowed is 1, neither "cc" nor "dd" can occur anywhere in a valid sequence. The only possible sequences are thus "cdc" and "dcd".
1)

   
10
{"c"}
1
Returns: 1
There is exactly 1 sequence with 10 shots that contains no cross-court shots.
2)

   
10
{"c"}
2
Returns: 11
There are 11 sequences that contain at most 1 cross-court shot.
3)

   
18
{"c","d"}
1
Returns: 0

4)

   
18
{}
1
Returns: 262144

5)

   
18
{"c","cc","ccc","cccc","ccccc","cccccc","ccccccc",
 "cccccccc","ccccccccc","cccccccccc"}
100
Returns: 262122

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.

This is a classic brute force problem. We have a space of possible solutions, namely all strings of 'c's and 'd's of a given length. We have to count all those that satisfy certain conditions. The method I advocate, due to its simplicity, is called generate-and-test. As it sounds, we will generate all potential strings, and test each for viability. To help explain the details of this process, I will illustrate two distinct methods that produce the correct results. In the first method, we use binary strings to represent possible stroke sequences. 1 will represent 'c', and 0 will represent 'd'. This works, since every sequence corresponds to exactly one binary string. For example, "cdcdcccddd" will correspond to "1010111000". The reason we introduce binary strings in the first place, is since we can loop through them in a very natural manner. Java code follows:

public int howMany(int numLength, String[] forbidden, int allowed) {
int ret = 0; //to be returned
for (int gen = 0; gen < (1<<numLength); gen++) { //Generate 000... through 111...
char[] buffer = new char[numLength];
for (int digitMask = 1,j=0; digitMask < (1<<numLength); digitMask *= 2,j++) { //loop through bits
if ( (digitMask & gen) != 0 ) buffer[j]= 'c'; //test mask against binary string
else buffer[j]= 'd';
}
String correspond = new String(buffer);
int countForb = 0; //counts forbidden sequences
for (int charPos = 0; charPos < correspond.length(); charPos++)
for (int forbIndex = 0; forbIndex < forbidden.length; forbIndex++)
if (correspond.startsWith(forbidden[forbIndex],charPos)) countForb++;
if (countForb < allowed) ret++;
}
return ret;
}
Notice how digitMask will assume the values 1,10,100,1000,... in sequence. Using the bitwise-and operation, digitMask allows me to test whether a given bit is 1 in gen. After correspond has been built, I loop through looking for substrings that match elements of forbidden. My final if statement tests for whether the string has few enough forbidden sequences. Also, the char[] buffer was used to avoid building a lot of Strings. Even with that slight optimization, this method just barely runs in time (approx. 6 seconds on some test cases).

In the second method, we search through the possible strings in a recursive fashion. Among other things, this allows us to eliminate groups of bad strings early on. Java code follows:
public int howMany(int numLength, String[] forbidden, int allowed) {
return rec(0,numLength,"",allowed, forbidden);
}

int rec(int index, int numLength, String curr, int allowed, String[] forbidden) {
if (index == numLength) return 1; //Base case
int ret = 0; //to be returned
for (char stroke = 'c'; stroke<='d'; stroke++) {
int newAllowed = allowed;
String newCurr = curr+stroke;
for (int forbIndex = 0; forbIndex < forbidden.length; forbIndex++) {
if (newCurr.endsWith(forbidden[forbIndex])) newAllowed++;
}
if (newAllowed <= 0) continue;
ret+=rec(index+1, numLength, newCurr, newAllowed, forbidden);
}
return ret;
}
The recursive function rec tries each possible character ('c' or 'd') for each position. It is also optimized to look for forbidden sequences at each recursion level. This way a generated string with a bad prefix will be ignored early on, thus accelerating the process. Understanding the recursive method may require more thought for some. When I think about how this function works, I consider how it behaves on a given index. Basically, when rec is called and index is k, it considers strings that have either 'c' or 'd' in position k. The concept lurking between the lines of code states that all strings can be divided in to 2 groups. Strings with 'c' in position k, and strings with 'd' in position k.

你可能感兴趣的:(visio)