dynamic

advertisement
DYNAMIC PROGRAMMING
All-Pairs Shortest Path
public class AllPairsShortestPath
{
static double[][] adjMatrix;
//read edges from input as an adjacency matrix
static void findPaths(int n) //n is the number of edges
{
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
adjMatrix[i][j] = Math.min(
adjMatrix[i][j], adjMatrix[i][k] + adjMatrix[k][j]);
}
static void initGrid() //set all paths to infinite
{
for (int i = 0; i < adjMatrix.length; i++)
for (int j = 0; j < adjMatrix[i].length; j++)
adjMatrix[i][j] = Double.POSITIVE_INFINITY;
}
}
1
Task Selection
Your work schedule for tomorrow consists of n tasks. Each task i has a specified start time (si), finish time (fi), and a
payment value (vi). In other words, if you choose to perform task i, you have to start the task at time si, finish it at time
ti, and you will be paid vi dollars for the task. Design an algorithm to compute your maximum earnings for tomorrow by
selecting the right subset of tasks. Clearly, you will not be able to perform all n tasks since tasks may overlap in time.
Example: The tasks may be given to you in random order. Here is a simple example.
Suppose that your tasks for tomorrow are as follows:
Task 1 Start time: 1100; Finish time: 1330; Value: $500.
Task 2 Start time: 1000; Finish time: 1500; Value: $750.
Task 3 Start time: 1330; Finish time: 1530; Value: $500.
Task 4 Start time: 1130; Finish time: 1430; Value: $750.
Then it is most proitable for you to take on Tasks 1 and 3, with earnings of $1000. No other subset of tasks can generate
higher earnings for you.
Designing the Algorithm: Write down pseudocode (algorithm) to solve this problem using dynamic programming by
following the steps outlined below:
1. Identify clearly all he subproblems that you wish to solve. Note: Subproblems must be carefully designed to represent
a collection of problems, ranging from easier to harder problems. Also, one of the subproblems should solve the entire
problem.
2. Write down a recurrence relation that states how to solve a given subproblem in terms of easier subproblems. Note:
The easiest subproblem can be solved directly and does not need to be written in terms of any easier subproblems.
3. Specify the order in which you should solve the subproblems. Note: This order is specified by the recurrence relation
above and usually goes from easier to harder subproblems.
4. Now write down the necessary pseudocode to solve the problem.
Solution to the Task Selection Problem
1. If we want to solve this problem using dynamic programming, we need to identify a set of appropriate
subproblems. The obvious set of subproblems is to solve the same problem (compute the maximum
earnings for tomorrow by selecting a set of non-overlapping tasks from the given set of tasks), but on
smaller subsets of tasks of size varying from 1 to n. So, if the original set of tasks is {T 1, … Tn}, then the
subproblems we could consider would take as inputs the subsets {T1}, {T1, T2}, … {T1, … Tn}. Clearly,
this list of subproblems goes from easier to harder ones and the last one is the problem we want to
solve. However, with this choice of subproblems, you will discover that it is hard to write the
recurrence relation in problem 2. Why? For the i-th subproblem involving the input {T1, … Ti}, how
can we use the solutions to the easier subproblems? If task Ti is in the solution, we have to consider a
subproblem that involves all tasks that do not overlap with it. However, this subproblem may not be
one of the subproblems we have listed, which means that we now have to solve other subproblems
not listed above. In the final analysis, you will be solving the original problem on every subset of tasks
2
in the original input. Since we were given n tasks, the number of different subsets is very very large
(2n). Assuming n can be as high as 100, this is not a feasible approach to the problem.
So let’s reconsider this question. Suppose we sort the tasks in increasing order of finish times. Let the
new task list in sorted order be t1, …, tn. Now let the subproblems consider the subsets {t1}, {t1, t2}, …
{t1, … tn}. Once again, the list of subproblems goes from easier to harder ones and the last one is the
problem we want to solve. The difference this time is that it is going to be easier to answer question 2
below. Before, we proceed further to the next question, let’s define the following notation:
Let S[i] = value of the optimal solution to the i-th subproblem. In other words, S[i] is the sum of
the values of the tasks chosen for the optimal solution to the i-th subproblem. Note that S[1] = v1 (do
you understand why) and S[n] is what we need to report.
We will add one more trivial subproblem, i.e., that of solving the problem on the empty set. You will
see later why we need this little detail.
2. Here we are asked to write down the recurrence relation for S[i].
Simple observation: We need to deal with only 2 cases. The optimal solution to the i-th subproblem
either includes task ti or does not include task ti.
Case 1: If the optimal solution to the i-th subproblem does not include task ti, then solving the i-th
subproblem, i.e., finding the optimal solution on subset {t1, … ti}, is the same as finding the optimal
solution on subset {t1, … ti-1}. This is equivalent to saying that if ti is not part of the optimal solution,
we might as well assume that it is not part of the input to this subproblem. However, finding the
optimal solution on subset {t1, … ti-1} is exactly the (i-1)-th subproblem, which is an easier subproblem
that we could solve prior to solving the i-th subproblem.
Case 2: If the optimal solution to the i-th subproblem does include task ti, then solving the i-th
subproblem can be solved by setting aside task ti (which we have decided to include in our optimal
solution), remove task ti and all tasks that overlap with it, and combine it with the optimal solution to
the subproblem on the remaining tasks. Let Hi be the subset of {t1, … ti-1} after removing all tasks that
overlap with task ti. Is this one of the subproblems listed in question 1? BINGO! It is one of the
subproblems listed above. Let me now try to convince you that it is so. It is useful for you to draw a
picture of the subset of i tasks that finished first from among the tasks {t 1, … tn}. Since the tasks were
sorted by finish times, this is exactly the list t1, … ti Note that t1 finishes before t2, which finishes
before t3, … , which finishes before ti-1, which finishes before ti. All tasks that overlap with task ti
should be at the tail end of the ordered list t1, … ti-1. Thus the set Hi, which is the set of remaining tasks
must be equal to t1, … tk, where tk is the last task in that list that does not overlap with ti. Thus solving
the problem on subset Hi is the same as the k-th subproblem, which is on our list of subproblems
listed in question 1. It is easy to see that k must be smaller than i. In fact, it could be 0 too. It is exactly
to take care of the case when k = 0, that we added a subproblem when the input is the empty set.
Summarizing, we can reduce the i-th subproblem to either the (i-1)-th subproblem (Case 1) or the kth subproblem (Case 2).
We are now ready to write down the recurrence relation as follows:
S[i] = Max { S[i-1], S[k] + vi}, where tk is the last task in the input to the i-th subproblem that does not
overlap with task ti.
3. Ordering the problems is now trivial. In order to solve the i-th subproblem, we definitely need at least
the optimal solution to the (i-1)-th subproblem. Thus we will compute S[i], in the order from i=0 to
i=n.
4. Pseudocode is now a piece of cake – don’t you agree? Just to make it clear, let me spell it out.
a. Sort the tasks by finish times
b. S[0] = 0 and S[1] = v1
c. For i = 2 to n
// compute S[i] as specified in recurrence relation above.
k = i-1
3
while (fk > si) // does task tk overlap task ti?
k=k-1
S[i] = Max { S[i-1], S[k] + vi}
d. Report S[n]
4
// Let me count the ways - solution
import java.util.*;
public class Problem357 //Let me count the ways
{
static long[] ways = new long[30001];
static int[] denoms = {1, 5, 10, 25, 50};
public static void main(String[] args) throws Exception
{
Scanner in = new Scanner(System.in);
getWays();
while (in.hasNextInt())
{
int cents = in.nextInt();
System.out.println(ways[cents]);
}
}
public static void getWays()
{
ways[0] = 1;
ways[1] = 1;
for (int i=2; i<=30000; i++)
{
long ans = 0;
for (int j=0; j<denoms.length; j++)
{
if (i-denoms[j]>=0)
ans += ways[i-denoms[j]];
}
ways[i] = ans;
}
}
}
5
// DynamicCoins.java
// Dynamic programming example from Weiss Data Structures, Chapter 7
// Slightly altered, with comments by Irvine, 1/23/08
public class DynamicCoins {
/**
* Determine the minimum number of coins to use when making change for
* a range of values.
* @param coins array of possible coin values
* @param differentCoins number of different coin values
* @param maxChange largest change value
* @param coinsUsed array of minimum number of coins required for each
* change value
* @param lastCoin array of last coins used when making each change value
*/
public static void makeChange( int[] coins, int differentCoins,
int maxChange, int[] coinsUsed, int[] lastCoin )
{
coinsUsed[0] = 0;
lastCoin[0] = 1;
for( int cents = 1; cents <= maxChange; cents++ )
{
int minCoins = cents;
// assume the worst--all pennies!
int newCoin = 1;
// highest and last coin value used
// loop through
for( int j = 0;
{
// skip this
if( coins[j]
continue;
the coins array, trying each one
j < differentCoins; j++ )
coin if it is too large
> cents )
// subtract the current coin's value from cents and use the remainder
// as a subscript into the coinsUsed array. That array entry will tell us
// how many coins were required to produce the remaining value.
int x = cents - coins[j];
// if by adding 1 to the number of coins from the previous problem
// we can reduce the number of coins, then assign this new value to minCoins.
if( (coinsUsed[x] + 1) < minCoins )
{
minCoins = coinsUsed[x] + 1;
// minCoins is lower now
newCoin = coins[j];
// remember that we used this coin
}
}
// at this point, we have found the minimum coins required for
// this amount of change.
coinsUsed[ cents ] = minCoins;
lastCoin[ cents ] = newCoin;
// save reference to the last coin used
}
}
6
static void print( int[] array )
{
for( int i = 1; i < array.length; i++ )
{
String suffix = array[i] > 1 ? " coins" : " coin";
System.out.println( "Changing " + i + " requires at least " + array[i] + suffix );
}
}
public static void main(String[] args)
{
// this is the total change value that we wish
// create, using the smallest number of coins
int maxChange = 30;
int[] coins = { 1, 5, 10, 21, 25 };
int differentCoins = coins.length;
// holds a count of the minimum number of coins required
// to produce each value from 0 to maxChange.
int[] coinsUsed = new int[maxChange+1];
// keeps track of the highest coin value used when
// making change for each value from 0 to maxChange-1.
int[] lastCoin = new int[maxChange+1];
makeChange( coins, differentCoins, maxChange, coinsUsed, lastCoin );
// print the list of available coins
System.out.print("Coins: ");
for( int val : coins )
System.out.print( val + ", " );
System.out.println("\n-----------------");
// print the list of change counts
print( coinsUsed );
}
}
7
// Little Bishops.java
// By Jesus Ramos
import java.util.*;
import java.math.*;
public class Problem861 //Little Bishops
{
public static void main(String[] args) throws Exception
{
Scanner in = new Scanner(System.in);
while (in.hasNext())
{
int n = in.nextInt();
int k = in.nextInt();
if (n==0 && k==0)
break;
System.out.println(numCombos(n,k));
}
}
public static BigInteger numCombos(int n, int k)
{
/* eduardo gave me the idea when he wanted to turn the board on its side and i
realized that if you place a bishop
* on a white square it doesnt affect placement of bishops on black squares so if
you split up the board into
* 2 seperate pieces than this problem just becomes the rook problem on the wiki
article we looked at
*/
long[] board1 = new long[9];
long[] board2 = new long[9];
for (int i=1; i<=n; i++)
{
if (i%2==0)
board1[i] = board1[i-1];
else
board1[i] = i;
}
for (int i=1; i<=n-1; i++)
{
if (i%2==0)
board2[i] = board2[i-1];
else
board2[i] = i+1;
}
//rotating the board gives all the combinations of moves
long[][] boardCombos1 = new long[9][65];
long[][] boardCombos2 = new long[9][65];
for (int i=0; i<=n; i++)
boardCombos1[i][0] = 1;
for (int i=0; i<n; i++)
boardCombos2[i][0] = 1;
//this is the definition of the recurrence relation for the rooks problem
for (int i=1; i<=n; i++)
for (int j=1; j<=k && j<=i; j++)
boardCombos1[i][j] = boardCombos1[i-1][j] + boardCombos1[i-1][j-1] *
(board1[i] - j + 1);
for (int i=1; i<n; i++)
for (int j=1; j<=k && j<=i; j++)
8
boardCombos2[i][j] = boardCombos2[i-1][j] + boardCombos2[i-1][j-1] *
(board2[i] - j + 1);
//this could have been done using dynamic programming and then memoized all the
results in a table
//but im too lazy to do that right now
BigInteger sum = BigInteger.ZERO;
for (int i=0; i<=k; i++)
sum = sum.add(new BigInteger(""+boardCombos1[n][i]).multiply(new
BigInteger(""+boardCombos2[n-1][k-i])));
return sum;
}
}
9
10
Download