Download as pdf or txt
Download as pdf or txt
You are on page 1of 5

Senior Computer Team Lecture 2000-2001: Dynamic Programming II

”No Dynamite Involved”

Vladimir Novakovski

Dynamic programming is a technique to efficiently solve recursion problems – problems


whose solutions depend on solutions of smaller problems – by storing partial results. We
have seen how, given a recursion, DP can solve a problem efficiently. Now, we will look at
the harder part: deciphering a problem as DP. While this skill is based on intuition and
experience, what we can do is provide you with some classic examples. Many times on a
contest round, a working knowledge of these examples is very helpful. The first example we
will look at is “the classic DP problem” according to the USACO head coach Rob Kolstad.
Example 4: Buy Low, Buy Lower [Rob Kolstad]
Given an array of N numbers, N < 5000, determine the longest decreasing subsequence.
The first step is to formulate this as a recursion. We introduce the following function:
best(i) denotes the longest decreasing subsequence that ends at position i. Now, the last
element of the sequence is at i. The next-to-last element can be anywhere from 0 to i − 1,
but it must be greater than the element at i. The subsequence that ends at the next-to-last
element is itself a decreasing subsequence, and should be the best possible. Therefore, we
have the recurrence relation
0≤j<i
best(i) = 1 + max best(j),
aj >ai

where a is the array of data. We also assume that best(0) = 1. This leads itself to DP
because we may compute the best(i) values by storing them in an array in order. The sample
program is as follows:

int solve()
{
int i, j, max = 0;
int best[MAXN];
best[0] = 1;
for (i = 1; i < N; i++)
{
best[i] = 1;
for (j = 0; j < i; j++)
{
if (a[j] > a[i] && best[i] < best[j] + 1) best[i] = best[j] + 1;
}
}
for (i = 0; i < N; i++)
if (best[i] > max) max = best[i];
return max;
}
Let’s analyze how this program processes the data [1, 6, 3, 7, 2, 6, 4, 2, 10, 3, 2, 1].
Index i Value of ai Value of best[i] Longest decreasing subsequence ending at i
0 1 1 {1}
1 6 1 {6}
2 3 2 {6,3}
3 7 1 {7}
4 2 3 {6,3,2}
5 6 2 {7,6}
6 4 3 {7,6,4}
7 2 4 {7,6,4,2}
8 10 1 {10}
9 3 4 {7,6,4,3}
10 2 5 {7,6,4,3,2}
11 1 6 {7,6,4,3,2,1}

So the answer is 6, and our program uses quadratic time and linear memory. We now look
at another true classic, the knapsack problem.
Example 5: Knapsack [Traditional]
A robber is in a store 1 where items of different size and value are available. He wants to
maximize total value, but all his items must have total size less than the size of his knapsack.
Write a program to find the maximum value he can obtain.
We use the following recursive function: rob(S, i), which tells you the maximum value the
robber can get if he has to fill up size S of his knapsack and he can only use the first i items.
It may not be obvious to you why we use this function. Don’t worry: you are not alone.
But, after we come up with a valid function, the recurrence is easy. When finding rob(S, i),
there are two possibilities: we either use item i or we don’t use item i. If we use item i, then
we now have a knapsack with size S − size(i) to fill up, and the first i − 1 items to use; the
best value would thus be value(i) + rob(S − size(i), i − 1). If we do not use item i, then we
have a knapsack of size S still, with the first i − 1 items to use. Then, the best value is just
rob(S, i − 1). Therefore,

rob(S, i) = max{rob(S, i − 1), value(i) + rob(S − size(i), i − 1)}.

This is plain to do using two-dimensional DP: simply loop through the values of i and
within each such loop, look at the values of S sequentially. In code,

int solve()
{
int i, S;
int best = 0;
for (i = 0; i < N; i++) // N is the total # of items
for (S = 1; S <= knapSize; S++)
{
if (S >= size[i])
rob[S][i] = max(rob[S][i-1], value[i] + rob[S-size[i]][i-1]);
1
Some students think that the “dynamic” programming solution to the problem would be to plant some
dynamite in the store and blow it up.
else rob[S][i] = rob[S][i-1];
}
for (S = 1; S <= knapSize; S++)
if (rob[S][N - 1] >= best) best = rob[S][N - 1];
return best;
}
This program is O(M N ), where M is the size of the knapsack, and uses O(M N ) memory
as well. To make it use less memory we can use the sliding window trick. But before we do
that, let’s analyze how this program processes the following data: items of sizes {1, 2, 3, 4, 5}
and values of {2, 2, 8, 7, 10}, and knapsack of size 10. Keep in mind that initially, the array
rob contains all zeroes.
The first i items i = 1 i = 2 i = 3 i = 4 i = 5
rob[0][i] 0 0 0 0 0
rob[1][i] 2 2 2 2 2
rob[2][i] 0 2 2 2 2
rob[3][i] 0 4 8 8 8
rob[4][i] 0 0 10 10 10
rob[5][i] 0 0 10 10 10
rob[6][i] 0 0 12 12 12
rob[7][i] 0 0 0 15 15
rob[8][i] 0 0 0 17 18
rob[9][i] 0 0 0 17 20
rob[10][i] 0 0 0 19 20

After tracing through the code, it should be evident how these values are obtained. We
can use the sliding window approach to solve the problem using linear memory; the idea is
that given rob[i][N ] for all i, we can find rob[i][N + 1]. Exact implementation is left to the
reader.
Our next example illustrates a non-trivial example of two-dimensional dynamic program-
ming, when a clever computation scheme is necessary as well. It is also from a very recent
contest, which means that DP is alive and still going strong.
Example 6: Palindrome [IOI 2000]
You are to write a program which, given a string, determines the minimal number of
characters to be inserted into the string in order to obtain a palindrome. The length of the
original string is at most 5000.
We note the following: if string S is a palindrome, then string S without its first and
last characters is also a palindrome. With that in mind, we define the following function:
pal[start][f inish], which denotes the shortest palindrome that can be obtained by inserting
characters into the substring that starts at start and ends at f inish. This function is a bit
complicated; take a moment to fully digest it.
Now, suppose that S is the string. If S[start] = S[f inish], then we can just peel off these
two characters, i.e., look at the substring that starts at start + 1 and ends at f inish − 1.
Otherwise, we must either insert a character after S[f inish] or before S[start] to make this
into a palindrome. In these cases, we have two equal characters, one already present and
one inserted, and the substring we have to consider is either the string starting at start and
ending at f inish − 1 , or the string starting at start + 1 and ending at f inish. So, the
following recursion is obeyed by pal:

pal[start][f inish] = pal[start + 1][f inish − 1] + 1 if S[start] = S[f inish];

pal[start][f inish] = 1 + min(pal[start + 1][f inish], pal[start][f inish − 1]) otherwise.


To solve this with DP, consider the following picture (keep in mind that start ≤ f inish):

To compute the value at a given square (start and f inish value), we need the values at
(start + 1, f inish), (start, f inish − 1), and (start + 1, f inish − 1), i.e., the square to the left,
the square to the bottom, and the bottom-left square. Thus, to find a given row, we only
need to know the previous row: this allows us to use linear memory (and quadratic time) to
solve the problem.
We may have gotten our hands dirty with DP, but we have only touched the tip of
the iceberg. The challenge problems provide some more examples, but to really hone your
DP skills, we recommend the USACOgate training system (http://ace.delos.com/usacogate),
the Universidad de Vallaloid online judge (http://acm.fi.uva.es/problemset) and, of course,
old national and international olympiads. Your “homework” should be to solve as many
challenge problems as you can. Also, writing your own problems is very good practice. So,
for the next SCT meeting, try to write a DP problem of your own, and be ready to share it
with the rest of the team.
Challenge Problems
1. Suppose that we have items of different size and value, but there is an unlimited
quantity of each item. Explain how to efficiently solve the knapsack problem in this case.
2. [Equal Summed Subsets; from USACO 2000, adapted by Rob Kolstad] Given the set of
integers {1, 2, 3, ..., N }, determine the total number of ways you can create two equal-summed
subsets of that set.
3. [Building Blocks; from USACO 2000, adapted by Hal Burch] The cows want to build
a tower out of the blocks they found. They want you to tell them how high the tower will
be, given the sizes of the blocks. Each block has a width and length in addition to being
one unit in height. The blocks must be stacked with all their edges parallel to a set of axes,
and the blocks can be rotated 90 degrees, if you wish. In order to build a stable tower, each
higher block must be no larger than the block on which it sits – both the width and the
length of each higher block must be no larger than the width and length of the block upon
which it sits.
Your job is to determine the height of the tallest possible stable tower that can be built
with a given collection of blocks.
4. [COPS – Cows on Pogo Sticks; from USACO 2000, Brian Dean] Bessie the Cow wants
to use a pogo stick to travel along a cow path of integer length, L. Bessie starts at point 0
and proceeds in integer pogo-jumps to land exactly on point L. Bessie’s velocity, V, starts
out at zero and is always nonnegative. At the beginning of each bounce, she can change her
velocity by -1, 0, or +1. The velocity is the distance Bessie travels along the path during
the bounce. Bessie can be traveling at any nonnegative velocity when she lands on point L.
Bessie wishes to avoid jumping on any of the cow pies (small heaps of manure) that happen
to be located at various integer points along the path (not including location 1 or location
L, of course).
Your job is to determine the smallest number of bounces to reach exactly the end of the
path, point L.
5. Prove that the length of the longest decreasing subsequence of array A is equal to
the minimum number of disjoint increasing subsequences that, when merged in a particular
order, give you A.
6. Using the result of problem 5, devise an algorithm that runs in O(N log N ) time to
solve the Buy Low, Buy Lower problem.

References:
1. Skiena, S. The Algorithm Design Manual
2. Danaher, J. ”Dynamic Programming”, 1999-2000 SCT Lecture.
3. Kolstad, R. et al. ”Dynamic Programming”, 1999 USACO Traning Camp.
4. Cormen, T. et al. Introduction to Algorithms

You might also like