The basic outline of cvg() will be:
long long OneDimensionalBalls::cvg(int v, int index)
{
long long rv;
rv = 0;
for (i = -1; i <= 1; i += 2) {
if (firstPicture[index]+v*i is in secondPicture) {
if (index+1 == firstPicture.size()) {
rv++;
} else {
remove the ball from secondPicture that equals firstPicture[index]+v*i;
rv += cvg(v, index+1);
put the ball back
}
}
}
return rv;
}
|
This solution will work for small sizes, but the fact that it returns a long long should clue you in that something isn't right. Regardless, it's worth hacking up for practice. Here is a first pass, in ODB_Rec1.cpp. First, I save firstPicture and secondPicture as FP and SP. That way, my recursive procedure always has access to them. I also have two more members: Matched is a vector, which is the same size as SP. If Matched[i] is true, then element i of SP has been matched from a previous call to cvb. SP_Balls is a map such that if SP[i] = j, then SP_Balls[i] = j. It lets us find out quickly whether a ball with a given value is in SP.
class OneDimensionalBalls {
public:
long long countValidGuesses(vector <int> firstPicture, vector <int> secondPicture);
long long cvg(int v, int index);
vector <int> FP;
vector <int> SP;
vector <int> Matched;
map <int, int> SP_Balls;
};
|
Given that backdrop, cvg works as above, except it fills in the pseudo-code with code that uses Matched and SP_Balls:
long long OneDimensionalBalls::cvg(int v, int index)
{
long long rv;
int second_val, sb_index;
map <int, int>::iterator mit;
int i;
rv = 0;
for (i = -1; i <= 1; i += 2) {
second_val = FP[index]+v*i;
mit = SP_Balls.find(second_val);
if (mit != SP_Balls.end() && !(Matched[mit->second])) {
if (index+1 == FP.size()) {
rv++;
} else {
Matched[mit->second] = 1;
rv += cvg(v, index+1);
Matched[mit->second] = 0;
}
}
}
return rv;
}
|
Finally, the code for countValidGuesses() needs to set up FP, SP, Matched and SP_Balls, and just for testing, it calls cvg with a velocity of one:
long long OneDimensionalBalls::countValidGuesses(
vector <int> firstPicture,
vector <int> secondPicture)
{
int i;
FP = firstPicture;
SP = secondPicture;
Matched.resize(SP.size(), 0);
VIT(i, secondPicture) SP_Balls[SP[i]] = i;
return cvg(1, 0);
}
|
To compile and run this, I use my standard topcoder-programming trick of having a driver program that includes this program and runs the examples. This is in SRM-496-Driver.cpp. To compile, you need to copy your program to OneDimensionalBalls.cpp, and then simply compile SRM-496-Driver.cpp:
UNIX> cp ODB_Rec1.cpp OneDimensionalBalls.cpp UNIX> g++ SRM-496-Driver.cpp UNIX>With example zero, we have:
FP = { 12, 11 }
SP = { 10, 11, 13 }
|
Thus, when v=1, we should have two sets of legal guesses: 11 goes to 10 and 12 goes to either 11 or 13. When we run it, it gives us the correct answer:
UNIX> a.out 0 2 UNIX>You can also call a.out with -1 as an argument, and that allows you to put the two vectors of integers on standard input:
UNIX> cat tmp.txt 12 11 10 11 13 UNIX> a.out -1 < tmp.txt 2 UNIX>Now, what velocities should we use? The only ones that will be legal are velocities equal to the difference between each ball in SP and FP[0]. The code to enumerate these uses a set, because you don't want to test the same velocity twice (in the example above, the differences are 2, 1 and 1) (code in ODB_Rec2.cpp):
long long OneDimensionalBalls::countValidGuesses(
vector <int> firstPicture,
vector <int> secondPicture)
{
int i, d;
set <int> velocities;
set <int>::iterator vit;
long long rv;
FP = firstPicture;
SP = secondPicture;
Matched.resize(SP.size(), 0);
VIT(i, secondPicture) SP_Balls[SP[i]] = i;
VIT(i, SP) {
d = SP[i] - FP[0];
if (d < 0) d = -d;
if (d != 0) velocities.insert(d);
}
rv = 0;
IT(vit, velocities) rv += cvg(*vit, 0);
return rv;
}
|
When we run this, it works on all the test cases:
UNIX> cp ODB_Rec2.cpp OneDimensionalBalls.cpp UNIX> g++ SRM-496-Driver.cpp UNIX> a.out 0 3 UNIX> a.out 1 0 UNIX> a.out 2 1 UNIX> a.out 3 6 UNIX> a.out 4 7 UNIX>So compile and submit???? You should be worried. Each recursive case can make two recursive calls, and if there are roughly 25 levels of recursive calls, your code may take on the order of 225 operations. That's too much. Indeed, when I compiled and submitted, it choked on the following input:
FP = { 9, 10, ..., 39, 40 }
SP = { 1, 2, 3, ..., 49, 50 }
|
I put that into case 6 in SRM-496-Driver.cpp, and "a.out 6" takes much longer than the requisite 2 seconds. Dang, back to the drawing board. At least we've had some more practice with maps and sets.
When you learn about dynamic programming later, you'll see that memoization doesn't work here either.
FP = { 2, 4, 6 }
SP = { 1, 2, 3, 4, 5, 7, 8 }
|
We have 5 potential values of v: 1, 2, 3, 5 and 6. I draw the SP and their matching FP for the first four of these below:
![]() |
![]() |
![]() |
![]() |
Let's consider the simplest: v=5. In this case, 4 cannot be matched to anything, so there are zero valid guesses. (The same is true of v=6, which is not drawn).
Next, let's consider v=2. In this case, 2 and 4 match to one number in SP. We can thus remove them from the system. When we do so, 6 matches to one number, and we remove it. We are left with an empty system. That corresponds to one valid guess.
How about when v=3? Here, 2 and 6 each match to one number, so they can be removed from the system. We can also remove any balls in SP that don't match any balls in FP (at this point, the only ball in FP is 4), so we're left with:
![]() |
There are two ways to perform this match, so there are two valid matches. The last case is v=1, which requires a little more thought. Let's look for a pattern. When we have a system that looks like:
![]() |
As we said above, this corresponds to two valid guesses. Let's make it a little more complex:
![]() |
If we have the leftmost number in FP match the leftmost in SP, then there are two ways to match the remaining elements (since it's equal to the previous picture). If we have the leftmost number in FP match the second element in SP, then there is just one way to match the remaining element. That makes three ways in total.
Let's make it a little more complex still:
![]() |
If we have the leftmost number in FP match the leftmost in SP, then there are three ways to match the remaining elements (since it's equal to the previous picture). If we have the leftmost number in FP match the second element in SP, then there is just one way to match the remaining elements. That makes four ways in total.
Do you see the pattern? If you have a set of n elements in FP, then they will match to the elements in SP in n+1 ways. Thus, the v=1 case in Example 4 has four matches. The answer to example 4 is thus 4 + 1 + 2 = 7.
If we have multiple sets of matching elements, we have to multiply their number of guesses. For example, suppose we have:
![]() |
The first set matches in two ways, the second in four, and the third in three. Thus, the total number of matches is 2*4*3 = 24.
Let's take one final concrete and complex example to make sure that we understand. Suppose our system looks like:
![]() |
The first thing that we do is remove the numbers in FP (4, 15 and 22), that only match one number in SP. We also remove their matches in SP:
![]() |
We're left with more numbers in FP (2 and 20) that only match one number in SP. We keep removing them and their corresponding numbers in SP until we only are left with numbers that have two corresponding numbers in SP:
![]() |
From our discussion above, this one has four ways to be matched.
class Ball {
public:
int val;
Ball *low;
Ball *high;
};
|
Val is its value, and low and high are pointers to other balls. If b is in FP, then low points to the ball in SP whose values is b->val-v. If there is no such ball, then it is NULL. Similarly, b->high points to the ball in SP whose values is b->val+v. If there is no such ball, then it is NULL.
The balls in SP point to the balls in FP. To be precise, If b is in SP, then b->low points to the ball in FP whose values is b->val-v. If there is no such ball, then it is NULL. b->high is defined similarly.
When we call cvg() we just call it with a velocity. We then set up the balls and their pointers. Then, we do the following: