Editorial for IOI '94 P1 - The Triangle
Submitting an official solution before solving the problem yourself is a bannable offence.
Let us introduce some notation to formalize the problem. The triangle consists of
A route starts with
Our first design decision concerns the input data. Two approaches are possible:
- Read all input data at the beginning of the program and store it internally for later use.
- Process the input data while reading it, without ever storing all of it together.
Because the amount of input data is not so large (at most
For
Program 1
It is now easy to write a recursive function, say MaxRS
, that computes the maximum route sum from an arbitrary position in the triangle:
function MaxRS(r, p: index): integer ;
{ return maximum sum over all down routes starting in T[r, p] }
begin
if r = N then MaxRS := T[r, p]
else MaxRS := T[r, p] + Max(MaxRS(r+1, p), MaxRS(r+1, p+1))
end { MaxRS } ;
The invocation MaxRS(1, 1)
then solves the problem. However, this program passes the first three or four tests only. For a triangle with Max
(for computing the maximum of two numbers) by writing it out into the body of function MaxRS
, but that will not help (enough).
This problem was included precisely because it is tempting to use recursion. The 'plain' recursive solution was intended to fail (on some of the tests).
Program 2
There is a standard technique to speed up such a recursive program, known by names such as dynamic programming, tabulation, memoization, and memoing. Whenever the function MaxRS
is called with actual parameters MaxRS
with these same parameters
We introduce a table MRS
that is initialized at MaxRS
can be modified as follows:
function MaxRS(r, p: index): integer ;
{ return maximum sum over all down routes starting in T[r, p] }
begin
if MRS[r, p] = -1 then
if r = N then MRS[r, p] := T[r, p]
else MRS[r, p] := T[r, p] + Max(MaxRS(r+1, p), MaxRS(r+1, p+1)) ;
MaxRS := MRS[r, p]
end { MaxRS } ;
It is still a simple program and now it is also fast enough, because not all routes are followed completely. A disadvantage of this solution is that it requires additional storage for the table. In fact, Program 2 uses at least twice the amount of data memory compared to Program 1 (such a trade-off between speed and memory usage is a common dilemma). You can squeeze the table into the upper-right triangle of the square array, but that would complicate the program needlessly.
Program 3
When program 2 is analyzed a bit further, it becomes clear that table MRS
is filled in a particular order. Assuming that in the call Max(E, F)
, expression
15
10 14
6 9 13
3 5 8 12
1 2 4 7 11
It is now a small step to come up with a program that fills table MRS
in a non-recursive way. Furthermore, this can be done in a more convenient order, say row by row from the bottom. That is, for five rows in the order:
15
13 14
10 11 12
6 7 8 9
1 2 3 4 5
The following procedure computes MRS
non-recursively (also called iteratively):
procedure ComputeAnswer ;
{ m := the maximum route sum }
var r, p: index ;
begin
for r := N downto 1 do
for p := 1 to r do
if r = N then MRS[r, p] := T[r, p]
else MRS[r, p] := T[r, p] + Max(MRS[r+1, p], MRS[r+1, p+1]) ;
m := MRS[1, 1]
end { ComputeAnswer } ;
(Of course, you can eliminate the if-statement by introducing a separate MRS
if we put T
must be adjusted because the input T
-values are numbers in the range MRS
-values possibly not.
Program 4
In Program 3, all input data must still be read and stored before the actual computation starts, because MRS
is computed from bottom to top. Routes in the triangle can, however, also be considered from bottom to top by flipping the direction. If we take that point of view, then we can say that:
- a route starts somewhere on the base at
for ; - it steps upward either left or right (on the boundary there is no choice), that is, from
, with and , it may step to if , and to if ; - it always terminates at
.
We now redefine MRS
:
if if and if
The case distinction can be avoided by defining
MRS
can be computed iteratively from top to bottom. Therefore it is not even necessary to store all input values. Only one row of MRS
needs to be stored. From this row and an input row, the next row of MRS
is computed (this computation is a little tricky). The final answer is the maximum of the last row computed.
Comments