CS302 -- Lab B -- Dynamic Programming

This lab has two parts. I'm going to give you the descriptions first, and then after all the descriptions, I'll give hints on how to turn the problem into a solution.

Part 1: Dice or No Dice

Your friend Maui Handel comes to you with an idea for a new game show (or perhaps Vegas gambling game) called "Dice or No Dice." The way it works is as follows. A contestent will be given a s-sided die numbered from 1 to s, and a certain number of times that they must roll that die. Call that number t. On each roll after the first, if the contestant rolls a number that equals the previous number that they rolled, or is one away from the number that they previously rolled, they lose. If they roll the die t times without losing, they win a certain amount of prize money.

As an extra twist, Maui says that after each roll, he can offer them a certain amount of prize money for them to quit. Perhaps each roll will be represented by a model wearing glittery clothes. He hasn't figured out those details yet. Regardless, he would like to be able to know the probability of success at each phase of the game so that he may set odds. That's where you come in.

Maui wants you to write a program called dond.cpp that will be called with three parameters:

UNIX> dond s t last-roll

If last-roll is -1, then the program should print the probability of a contestant rolling an s-sided die t times succesfully. Otherwise, it should assume that the contestant has last rolled the value in last-roll, and we now want the probability of rolling the die t more times successfully.

Your program's output should match mine, which does error-check the command line parameters. The solution is a straightforward dynamic program with memoization if you want to structure it that way. You can also ditch the recursion and make it a straightforward iterative program. My solution was dynamic, because it required less thinking.

I won't give any hints or help here -- if you want some help, read the help at the end of this file.

For the grading, your program's output needs to match mine exactly on legal input.


Part 2: Zigzag paths

I could motivate this with a cute example, but I'm short on inspiration, so you get an abstract problem. Write the program bigzigzag.cpp. This program should take any number of doubles on standard input and put them into a vector v. Its job is then to find the longest zigzag sequence in v, where a zigzag sequence s is a sequence of numbers, s0, s1, s2 ... that adheres to to following three properties:

  1. For all i, si ≠ si+1.
  2. For all i, if si < si+1, then si+1 > si+2.
  3. For all i, if si > si+1, then si+1 < si+2.

In other words, the numbers alternate in size.

For example, (1, 4, 2, 3, 0) is a zigzag sequence. (1, 4, 5) is not. (1, 1, 4) is not. (1) is.

Your program must print the longest zigzag sequence in v, one number per line. If there are multiple zigzag sequences that are the same size, any one will do.

There are some example files in the lab directory on which to test. There are also two helper programs: islegalzigzag tests whether the zigzag path on standard input is a legal zigzag sequence of the values in the file on the command line. tfill is a program that I use to fill lines of text so that they take up 70 columns -- it is helpful to make output like that of bigzigzag readable. You can use wc to test and see if your zigzag path is as long as mine, and islegalzigzag to make sure that it is legal.

UNIX> cat v1.txt
1 4 2 3 0
UNIX> bigzigzag < v1.txt | tfill
1 4 2 3 0
UNIX> bigzigzag < v1.txt | wc
       5       5      10
UNIX> bigzigzag < v1.txt | islegalzigzag v1.txt
Legal
UNIX> cat v2.txt
6 4 0 1 9 6 4 0 2 6 5 
UNIX> bigzigzag < v2.txt | tfill
4 1 4 2 6 5
UNIX> bigzigzag < v2.txt | islegalzigzag v2.txt
Legal
UNIX> cat v3.txt
4 5 8 5 4 4 9 9 1 6 7 1 3 1 3 0 7 4 8 4 2 0 9 3 1 9 4 4 4 7 0 0 5 3 1
0 7 1 4 8 8 3 3 0 2 1 4 5 4 0 2 5 5 2 4 7 5 8 5 7 5 8 3 3 2 9 0 9 7 5
8 3 2 5 5 1 8 3 7 5 5 8 6 9 9 2 4 4 9 6 0 7 8 2 0 7 2 0 3 1 7
UNIX> bigzigzag < v3.txt | tfill
4 5 4 9 6 7 1 3 1 3 0 7 4 8 0 3 1 9 4 7 0 1 0 7 1 3 0 2 1 4 2 5 4 7 5
8 5 7 5 8 2 9 0 7 5 8 2 5 1 8 3 7 5 8 6 9 4 6 0 2 0 2 0 3 1 7
UNIX> bigzigzag < v3.txt | wc
      66      66     132
UNIX> bigzigzag < v3.txt | islegalzigzag v3.txt
Legal
UNIX> wc v4.txt
      59    1001    4004 v4.txt
UNIX> head v4.txt
2.4 8.5 9.5 5.9 7.6 7.6 3.9 9.7 4.4 6.5 4.3 8.9 5.1 6.1 5.7 4.6 9.6
0.4 0.8 0.8 6.3 4.9 2.0 9.0 0.3 5.4 5.4 0.9 7.3 3.8 9.3 2.0 7.0 9.2
2.0 6.6 0.2 7.0 4.3 7.4 1.5 5.8 0.8 9.8 7.5 8.7 0.0 3.4 1.6 3.4 6.4
6.2 5.8 5.4 9.4 1.7 7.9 8.3 7.9 4.8 3.3 3.4 8.9 9.4 9.5 3.6 1.3 0.3
6.1 4.2 9.7 6.2 4.6 5.5 2.4 5.9 4.9 6.5 4.9 1.7 9.7 7.4 6.6 2.4 3.4
0.7 8.7 5.2 0.0 6.8 0.9 0.5 6.0 5.4 0.7 3.8 9.5 5.3 9.0 3.6 2.2 1.8
6.0 5.4 1.5 7.3 3.9 0.7 1.5 8.2 4.3 0.1 3.5 1.6 2.2 6.9 3.8 1.2 1.9
9.5 2.0 0.3 2.4 1.5 2.9 6.6 6.3 9.0 0.3 1.1 7.5 4.9 6.4 8.6 5.5 3.0
8.7 9.2 9.9 3.2 5.4 1.4 6.0 0.0 7.4 5.4 6.0 3.7 2.2 2.8 7.8 7.9 8.0
8.8 3.2 2.0 8.5 5.0 9.1 8.9 9.1 0.5 5.5 4.5 4.7 3.7 1.9 4.7 8.9 9.7
UNIX> bigzigzag < v4.txt | wc
     664     664    2502
UNIX> bigzigzag < v4.txt | islegalzigzag v4.txt
Legal
UNIX> wc v5.txt
     589   10001   40004 v5.txt
UNIX> head v5.txt
6.1 0.5 3.3 2.0 9.2 3.3 1.0 5.2 9.9 9.7 7.2 6.8 6.8 1.7 0.4 0.8 6.4
2.3 5.2 0.6 8.2 5.0 5.7 1.8 2.9 8.2 7.3 8.6 0.9 2.0 7.0 5.8 5.0 5.5
8.3 7.3 2.8 6.4 6.5 3.8 8.8 4.5 6.4 1.1 7.3 3.0 5.0 9.5 6.7 5.6 1.1
1.8 6.3 9.7 5.5 8.4 3.0 3.5 4.0 7.5 8.1 5.5 6.8 0.0 0.7 5.4 0.5 5.9
3.4 5.1 4.0 4.9 1.2 7.6 3.3 6.5 2.6 1.5 7.2 1.9 4.2 9.5 1.6 9.9 3.5
0.5 9.2 7.8 7.4 0.8 3.4 9.8 9.2 6.7 0.3 6.2 7.4 0.7 0.9 6.3 2.6 8.7
9.1 9.3 9.5 7.7 2.3 8.3 1.7 2.8 0.3 0.3 3.0 0.2 7.0 4.4 9.0 4.5 7.8
2.1 5.8 6.3 3.2 0.9 6.0 9.5 8.8 0.2 2.5 5.1 1.1 8.5 2.6 3.0 3.2 7.1
4.2 9.6 7.2 7.2 6.7 1.2 1.4 8.1 1.7 4.0 9.2 1.3 2.4 4.4 9.8 6.3 6.8
7.0 3.4 7.1 7.5 8.6 4.9 7.6 7.8 5.0 9.3 5.1 3.6 4.1 9.5 5.6 2.8 8.7
UNIX> bigzigzag < v5.txt | wc
    6580    6580   24980
UNIX> bigzigzag < v5.txt | islegalzigzag v5.txt
Legal
UNIX> time bigzigzag < v5.txt > /dev/null
12.074u 0.025s 0:12.10 99.9%    0+0k 0+0io 0pf+0w
UNIX> bigzigzag < v5.txt > v5p.txt
UNIX> time islegalzigzag < v5p.txt v5.txt
Legal
0.707u 1.047s 0:01.79 97.2%     0+0k 0+0io 0pf+0w
UNIX> 
That's pretty slow -- you could make it a lot faster, but I won't so as not to set the bar too high for y'all. As before, I have help at the end of the file if you want it.

For the grading, your program simply needs to output the sequence with any formatting (as long as there are lines/whitespace between the numbers), and the sequence needs to be the correct length and pass the program islegalzigzag.


Help with dond.cpp

Let's look at some examples that should help you think about the solution. In each, we'll assume a six-sided die. If t is one, then all rolls are legal. If t is one, and last-roll is 0 or 5, then there are four legal rolls, yielding a probability of 2/3. If last-roll is any other value (1, 2, 3, 4), then there are only three legal rolls, yielding a probability of 1/2. These are all confirmed by the following dond calls:

UNIX> dond 6 1 -1
1
UNIX> dond 6 1 0
0.666667
UNIX> dond 6 1 1
0.5
UNIX> dond 6 1 2
0.5
UNIX> dond 6 1 3
0.5
UNIX> dond 6 1 4
0.5
UNIX> dond 6 1 5
0.666667
UNIX> 
So, consider t=2 and last-roll=-1. This will be the sum of times that t=1 and lastroll ≠ -1, divided by six. Put another way, you have a 1/6 probability of rolling a zero. If you roll a zero, you have a 2/3 chance of winning. Ditto when you roll a five. If you roll a 1 through 4, you have a 1/2 chance of winning. So, your probability of winning when t=2 is 1/6 * (2/3 + 2/3 + 1/2 + 1/2 + 1/2 + 1/2). That value is 0.555556:
UNIX> dond 6 2 -1
0.555556
UNIX> 
Now consider t=2 and last-roll=0. Well, 1/6 of the time, you will roll a zero on your next roll, and 1/6 of the time, you will roll a one. In each of those, you lose. However, 1/6 of the time, you will roll a 2, and your chance of winning will be "dond 6 1 2", which is 1/2. The same is true when you roll a 3 or a 4. 1/6 of the time, you'll roll a 5, and your chance of winning will be 2/3. Therefore, if t=2 and last-roll=0, your chance of winning will be 1/6*(0 + 0 + 1/2 + 1/2 + 1/2 + 2/3). That value is 0.361111:
UNIX> dond 6 2 0
0.361111
UNIX> 
You should be able to confirm all of the calls below by hand. For example, dond 6 2 1 will equal 1/6*(0 + 0 + 0 + 1/2 + 1/2 + 2/3), and dond 6 2 2 will equal 1/6*(2/3 + 0 + 0 + 1/2 + 1/2 + 2/3).
UNIX> dond 6 2 1
0.277778
UNIX> dond 6 2 2
0.305556
UNIX> dond 6 2 3
0.305556
UNIX> dond 6 2 4
0.277778
UNIX> dond 6 2 5
0.361111
UNIX>
The recursion should be getting clearer. dond 6 2 2 will equal 1/6*(0.361111 + 0.277778 + 0.305556 + 0.305556 + 0.277778 + 0.361111) = 0.314815:
UNIX> dond 6 3 -1
0.314815
UNIX> 

Help with bigzigzag.cpp

You should structure this like the Maxseq problem from the
dynamic programming lecture notes. You should maintain four arrays: You can either build these arrays up from 0 to v.size()-1, or you can build them backwards using recursion and memoization. Let's just look at a very simple example from v2.txt, where v is (6, 4, 0, 1, 9, 6, 4, 0, 2, 6, 5).

i v[i] up[i] down[i] up_path[i] down_path[i]
0 6 1 1 -1 -1
1 4 1 2 -1 0
2 0 1 2 -1 1
3 1 3 2 2 1
4 9 3 1 3 -1
5 6 3 4 3 4
6 4 3 4 3 5
7 0 1 4 -1 6
8 2 5 4 7 6
9 6 5 4 8 4
10 5 5 6 8 9

Look at the line where i=1. Since 4 is less than 6, there is no subsequence ending with that 4 where 4 is greater than the previous value. For that reason, up[1] = 1. However, there is a subsequence which ends with 4 where 4 is less than the previous value, and thus down[1] = 2.

When you set up[i], down[i], up_path[i] and down_path[i], you will need to check all smaller values of i. When you think you have it right, make sure you check your output against v3.txt, v4.txt and v6.txt. I have all the arrays in v1.html, v2.html, v3.html, v4.html and v6.html. The program bzz-html creates these files. When you're having problems, check your arrays against these.