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