Sunteți pe pagina 1din 43

Dynamic Programming

Louis Siu
13.9.2007

What is Dynamic
Programming (DP)?

Not a single algorithm


A technique for speeding up
algorithms (making use of
memory)
Closely related to recursion

Why learn DP?

DP problems appear in most, if not


all, programming contests.
Learning some common ways of
making use of DP is easy but often
sufficient.
DP programs are usually easy to
code.
Good at DP => good at recursion

Dynamic Programming
Idea

Fibonacci number f(n):

f(0) = 0
f(1) = 1
f(n) = f(n 1) + f(n 2), n > 1

Code segment for finding f(n):


int f(n){
if (n == 0) return 0;
if (n == 1) return 1;
return f(n-1) + f(n-2);
}

Computing f(4)
f(4)
f(3)
f(2)
f(1)

f(2)
f(1)

f(1)

f(0)

f(0)
f(0), f(1), f(2) are computed multiple times!
Exponential time for finding f(n)!

Modification

Memorize computed result:


int table[n];
int f(n){
if (table[n] is filled)
return table[n];
if (n == 0) return 0;
if (n == 1) return 1;
table[n] = f(n-1) + f(n-2);
return table[n];
}

Computing f(4)
f(4)
f(3)
f(2)
f(1)

f(2)
f(1)

f(1)

f(0)

f(i) are computed once only.


Linear time for finding f(n)!

f(0)

Dynamic Programming
Idea

Look for redundancy (recomputation) in the algorithm


Memorize intermediate results

Example 1 Tiling

Tiling (UVa #10359)

Given a board of size 2n


Available tiles: 21, 22
Find the number of ways to
completely fill the board.
E.g. when n = 3, 5 ways

Recurrence

Consider board of width n:


n

How can we fill the right-most column?

Recurrence

Let f(n) be the number of ways of filling a board of size


n2.
f(n) = f(n1) + f(n2)
+ f(n2)
f(n1) ways
to fill

f(n2) ways to fill

Base Cases

When n = 0, f(n) = 1.
When n = 1, f(n) = 1.

Overlapping Subproblems
f(n 3) ways to fill

f(n 3) ways to fill

Memorization can avoid re-computating f(n3).

Implementation 1

Bottom-up approach:
int ways[251];
int main(){
ways[0] = 1;
ways[1] = 1;
for (int i = 2; i <= 250; ++i)
ways[i] = ways[i-1] +
2 * ways[i-2];
}

Implementation 2

Top-down approach:
int ways[251];
int f(int n){
if (ways[n] != -1) return ways[n];
return (ways[n] = f(n-1) + 2*f(n-2));
}
int main(){
ways[0] = 1;
ways[1] = 1;
for (int i = 2; i <= 250; ++i)
ways[i] = -1;
// cout << f(250) << \n;
}

Bottom-up vs. Top-down


Order of
evaluation
Function
calls
Space
requirement
Redundant

Bottom-up
Important
No

Top-down
Not
important
Yes

Low

High

Possible

Never

Remarks

1 n 1
n

1
)
The number of solutions is
3

Therefore, any algorithm that counts


the number of solutions one by one
is exponential.
With memorization, f(i) is computed
once only in constant time the time
complexity of our DP solution is O(n).
Solve Tiling with big integer.

Example 2 Tri Tiling

Tri Tiling (UVa #10918)

Given a board of size 3n

Available tiles: 21
Find the number of ways to completely
fill the board.
Let f(n) be the number of ways to fill a
board of size n3
What is the relationship between f(n)
and f(n1), f(n2), f(n3), ?

Example

The following way of filling the


board cannot be counted using f(n
1), f(n2), f(n3),

Recurrence

How to fill the last column?

g(n1) ways to fill

f(n2) ways to fill

g(n) is the number of ways to fill a board of size n3 with one square at
the corner filled
f(n) = 2 g(n1) + f(n2), f(0) = 1, f(1) = 0

Recurrence for g(n)

How to fill the last column?

f(n1) ways to fill

g(n2) ways to fill

g(n) = f(n1) + g(n2), g(1) = 1, g(2) = 0

Remarks

Brute-force algorithm requires


exponential time to finish.
With memorization, f(i) and g(i) are
computed once only in constant
time the time complexity of our
DP solution is O(n).
g(n) is the key for formulating the
recurrence.

Example 3 Little Stone


Road (modified)

Little Stone Road (minicontest)

Input: an nm grid with positive


integers c(i, j)
Output: the minimum cost of any
path from any square in the top row
to any square in the bottom row (can
move down, left, right)
E.g. minimum cost = 6
1
6

2
4

3
7

Attempt 1

Observations

Many paths overlap significantly


There are only 3 possible incoming squares that lead to square (i, j)
top, left and right

f(i, j) = the min. cost of any path from the top to square (i, j)
E.g. f(1, 1) = 16 (green path)

7
4

16
5

f(i, j) = min { f(i-1, j), f(i, j-1), f(i, j+1) } + c(i, j)


Base cases: f(0, j) = c(0, j)
Answer = min { f(n-1, 0), f(n-1, 1), , f(n-1, m-1) }
Problems?

Attempt 1 Problems

f(i, j) = min { f(i-1, j), f(i, j-1), f(i, j+1) } +


c(i, j)
Base cases: f(0, j) = c(0, j)
f(i, j) queries f(i, j1), but they are
problems of the same size no progress
towards the base case.

Attempt 2

Observation: The paths that ends at square (i, j) can be can be classified
into n groups:

(i, j)

f(i, j) = min { f(i-1, 0) + c(i, 0) + c(i, 1) + + c(i, j),


f(i-1, 1) + c(i, 1) + c(i, 2) + + c(i, j),
f(i-1, 2) + c(i, 3) + c(i, 4) + + c(i, j),

f(i-1, j) + c(i, j),


f(i-1, j+1) + c(i, j) + c(i, j+1),
}
Base cases: f(0, j) = c(0, j)
Time complexity: O(mn2)

Attempt 3

Observation: the cost of horizontal moves are calculated many times

(i, j1)

(i, j)

Let L(i, j) = minimum cost to square (i, j) from the left or from
above, similarly for R(i, j)
L(i, j) = min { L(i-1, j), R(i-1, j), L(i, j-1) } + c(i, j)
R(i, j) = min { L(i-1, j), R(i-1, j), R(i, j+1) } + c(i, j)
Answer = min { L(n-1, 0), L(n-1, 1), , L(n-1, m-1),
R(n-1, 0), R(n-1, 1), , R(n-1, m-1) }
Fill in the base cases yourself!
Time complexity: O(mn)

Summary

Recurrence must reduce big


problems to small problems.
There can be more than one DP
formulation, with different
performance.

Example 4 Make
Palindrome

Make Palindrome (UVa


#10453)

A palindrome is a string that is not


changed when reversed.
Example of palindrome: MADAM
Example of non-palindrome: ADAM
By inserting M to ADAM, we get the
palindrome MADAM.
Find the minimum number of insertions
required to make a string a palindrome.
Show one palindrome produced using
the minimum number of insertions.

Idea

Let S[1..|S|] be the string.


Observation: if S[1] and S[|S|] do not
match, then an insertion either at the
beginning or at the end is required.
E.g. We MUST add M at the front or A
at the end to make ABCM closer to a
palindrome (MABCM or ABCMA)
The problem remaining is to make
ABC or BCM a palindrome.

Algorithm

f(i, j) = minimum number of insertions


required to make S[i, j] a palindrome.
int table[1000][1000];
int f(i, j){
if (i >= j) return 0;
if (table[i][j] is computed)
return table[i][j];
if (S[i] == S[j])
return table[i][j] = f(i+1, j-1);
return table[i][j] =
min(f(i, j-1), f(i+1, j)) + 1;
}

Backtrack get back the


resulting palindrome

Let S = ABCB
table[i][j]: min. # of insertions required to make S[i..j] a
palindrome.
table[i][j] = table[i+1][j-1] if S[i] = S[j]
min{ table[i+1][j]+1, table[i][j-1]+1 },
otherwise

j 1

table[1][4]
table[1][3]+1
table[1][4] =
table[2][4]+1

int table[1000][1000];
string S;
void backtrack(i, j){
if (i > j) return;
if (i == j) cout << S[i];
else if (S[i] == S[j]){
cout << S[i];
backtrack(i+1, j-1);
cout << S[j];
}
else if (table[i+1][j]+1 == table[i][j]){
cout << S[i];
backtrack(i+1, j);
cout << S[i];
}
else {
cout << S[j];
backtrack(i, j-1);
cout << S[j];
}
}

Remarks

Backtracking is done by checking how


the values in the DP tables are derived.
Further improvement:

S = ABAB
f(1, 2) is the same as f(3, 4), but they are
computed separately.
Store f(A), f(B), f(AB), f(BA), f(ABA),
f(BAB), f(ABAB) instead.

Need an efficient data structure to support this.

Example 5 Traveling
Salesman Problem

Traveling Salesman
Problem (TSP) (UVa
#10944)

There are n cities. The costs of


traveling between any pair of cities
i and j, c(i, j), are given.
The salesman wants to find the
minimum cost to visit all cities
exactly once and get back to the
starting city.
TSP is NP-complete.

Recurrence

Let f(S, i) = the minimum cost to visit all cities


in {1} S C, starting from city 1 and ending
at city i, where C is the set of all cities.

f ({i}, i ) c(1, i )
f ( S , i ) min { f ( S {i}, j ) c( j , i )}
jS , j i

Answer = f(C, 1)
Time complexity: O(2n n2) (much better than
exhausting all (n-1)! possible circuits).

Summary and Conclusion

DP is a powerful technique to improve running time of


algorithms.
The key idea of DP is avoiding re-computation by
memorization.
To make use of DP:

identify redundancy
define substructure (most difficult part)
formulate recurrence

Sometimes more than one DP formulations are possible.


DP solutions can be implemented top-down or bottom-up.
recover optimal solution by looking at how the entries in
the DP table are derived.
Alternatives to DP include brute-force with pruning and
greedy.
Practice is important.

Suggested Problems

Easy: 116, 147, 231, 847, 926, 988, 10036,


10081, 10192, 10198, 10304, 10337, 10359,
10400, 10404, 10453, 10532, 10617, 10651,
10702, 10759, 10891, 10912, 10970, 11151
Medium: 562, 607, 711, 714, 882, 10003,
10130, 10944, 10890, 10981, 11002, 11003,
11137
Hard: 757, 10020, 10564, 10663, 10859,
10911, 10934, 10940, 11045, 11081

S-ar putea să vă placă și