Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
TASK 2.2. GAME This problem is a typical example of the dynamic programming technique. In the following paragraphs we present four dynamic programming algorithms, each of which solves the problem. However, the four algorithms have four different time complexities and because of that they receive different number of points on the test data. 1. Basic Dynamic Programming: O(N4) We define a sub-problem (x,y) to be a snapshot of the game at a moment when there are x numbers left in the first sequence and y numbers left in the second sequence. We would like to find the minimal cost of every sub-problem (1 ≤ x ≤ L1 and 1 ≤ y ≤ L2), denoted by f[x][y]. The easiest way to do that is by trying all possible moves for the first move of the sub-problem. We can compute the minimal cost associated with a move using the following formula: f[x][y] = min{(S1-K1)*(S2-K2) + f[x-K1][y-K2]} for each K1 and K2 that satisfy the conditions. This is simply summing the cost of the move and the optimal cost of the remaining sub-problem. The minimum cost associated with all possible moves from subproblem(x,y) gives us the value of f[x][y]. This algorithm can easily be implemented in O(N4) time. Such algorithm receives between 20 and 30 points on the test data, depending on the particular implementation and the level of optimization. 2. Useful Observations: O(N3) There are several very useful observations, which can be made, so that we can simplify the problem and achieve a faster solution. The first such observation is that we can transform each sequence to a sequence which has the same numbers, with the only difference that all of them are decreased by one. The benefit of this is that instead of using the (S1-K1)*(S2-K2) formula, we can use the simpler S1*S2. From now on we will work with these modified sequences. Having this simplification in place we can easily see another one. Namely, that it does not make any sense to take two or more numbers from each sequence simultaneously (i.e., on each move we should pick either K1 = 1 or K2 = 1 or both). Here is a proof of the above: Let’s consider a possible move with K1 > 1 and K2 > 1. Taking at least two numbers from each sequence means that we can in fact divide this move into two moves. Let’s consider such two moves. The first one taking K1 numbers from the first sequence (with sum S1) and K2 numbers from the second sequence (sum S2). The second one taking K3 more numbers from the first sequence (sum S3) and K4 more numbers from the second sequence (sum S4). The total cost associated with taking these two moves separately is S1*S2 + S3*S4. If we were to take both moves as one move we would end up with a total cost of (S1+S3) * (S2+S4) = S1*S2 + S3*S4 + S1*S4 + S3*S2 > S1*S2 + S3*S4. This observation reduces the set of possible moves in each situation to size O(N). This in turn improves our dynamic programming algorithm to O(N3). This algorithm scores between 30 and 50 points on the test data. 3. Reducing the Innermost Loop: O(N3) / O(N2) We would like to improve the efficiency of our algorithm even more and in order to do that we will employ a well-known dynamic programming optimization technique, known as Reducing the Innermost Loop. Here is the explanation. Our current algorithm does the following: for each sub-problem (x,y) it tries two kinds of moves. The first kind is taking one number A from the first sequence and K2 numbers from the second sequence. The algorithm determines the optimal value for K2 by choosing the one which minimizes the cost A*S2 + f[x-1][y-K2](where A is the last number in the first sequence). Let’s denote the position in the second sequence where we find the optimum to be index1[x][y] (this is also equal to y-K2). We do the same for the second kind of moves (the one that minimizes S1*B + f[x-K1][y-1]) and find the position of the optimum to be index2[x][y]. Finally, the algorithm compares the two optimal moves (one from each kind) and chooses the better one. We will reduce the innermost loops of the above algorithm by making the following observation: Let’s denote index1[x][y-1] with G and index1[x][y] with H. We will prove that G≤H. Let’s assume that G>H. This means that H < G < y-1 < y. and so we consider both G and H when calculating f[x][y] and f[x][y – 1]. Moreover, when we were solving the subproblem (x,y)we preferred H to G. We also preferred G to H when we were solving (x, y – 1). This leads to: A*sb(G+1, y)+f[x-1][G] < A*sb(H+1, y)+f[x-1][H] A*sb(G+1, y-1)+f[x-1][G] > A*sb(H+1, y-1)+f[x-1][H] (We define the sum ot the numbers between positions u and v in the second sequence to be sb(u,v)) However, if we subtract S1*Sum(y,y) from the two sides of the first inequality, we end up with two contradictory inequalities and therefore our assumption that G>H is false. So index1[x][y-1]≤index1[x][y]. This means that when computing index1[x][y], instead of trying all possible values from 1 to y-1, we can try only the values from index1[x][y-1] to y-1. We can also do the same in the other innermost loop: when computing index2[x][y], instead of looping from 1 to x-1, we can loop only from index2[x-1][y] to x-1. This algorithm’s upper bound is still O(N3) in theory, but its practical time complexity is close to O(N2). That is why this algorithm is particularly suitable for our problem and it scores between 60 and 80 points on the test data. 4. Eliminating the Innermost Loop: O(N2) The above algorithm is really good, but it still does not reach a pure O(N2) time complexity. That is why we would like to optimize our dynamic programming solution even more and we will do it by completely eliminating the innermost loops. In the proof above we showed that if we compare two candidates for index1[x][y] and also compare the same candidates for index1[x][y-1] we can never end up with different results, because otherwise we get the above two inequalities with equal sides, but different signs. This means that index1[x][y] is either equal to index1[x][y-1] or is outside the range of valid values for index1[x][y-1]. The range of valid values for index1[x][y-1] is 1 to y-2 and the range of index1[x][y] is 1 to y-1. Hence, index1[x][y] is either equal to index1[x][y-1] or equal to y-1. All we have to do now is check these two possible values, which completely eliminates the innermost loop of our dynamic program. Also, we apply the same for index2[x][y] (here the two possible values are index2[x-1][y] and x-1). The last thing to keep in mind is to compute index1[x][2] and index2[2][y] in the usual way, as index1[x][1] and index2[1][y] are special cases and thus we are not able to apply the recurrent logic described above. The time complexity of this algorithm is clearly O(N2), as we perform O(1) operations for each sub-problem. Proper implementation of this algorithm is guaranteed 100 points on the test data. 5. Source Code Here is an implementation of the O(N2) algorithm, written in C. for (i = 1; i <= n; i++) f[i][1] = sa[i]*b[1]; for (j = 1; j <= m; j++) f[1][j] = a[1]*sb[j]; for (indexj = 1, j = 2; j <= m; j++) { if (a[2]*b[j] + f[1][j-1] < a[2]*(sb[j]-sb[indexj]) + f[1][indexj]) indexj = j - 1; if (a[2]*b[j] + f[1][j - 1] < sa[2]*sb[j]) indexi[j] = 1; else indexi[j] = 2; f[2][j] = MIN(a[2]*(sb[j] - sb[indexj]) + f[1][indexj], sa[2]*sb[j]); f[2][j] = MIN(f[2][j], a[2]*b[j] + f[1][j - 1]); } for (indexj = 1, i = 2; i <= n; i++) { if (a[i]*b[2] + f[i - 1][1] < (sa[i] - sa[indexj])*b[2] + f[indexj][1]) { indexj = i - 1; } if (a[i]*b[2] + f[i - 1][1] < sa[i]*sb[2]) startj[i] = 1; else startj[i] = 2; f[i][2] = MIN((sa[i]-sa[indexj])*b[2] + f[indexj][1], a[i]*b[2] + f[i - 1][1]); f[i][2] = MIN(f[i][2], sa[i]*sb[2]); } for (i = 3; i <= n; i++) { indexj = startj[i]; for (j = 3; j <= m; j++) { if (a[i]*b[j] + f[i - 1][j - 1] < a[i]*(sb[j] - sb[indexj]) + f[i - 1][indexj]) { indexj = j - 1; } if (a[i]*b[j] + f[i-1][j-1] < b[j]*(sa[i]-sa[indexi[j]]) + f[indexi[j]][j-1]) { indexi[j] = i - 1; } f[i][j] = MIN(a[i]*(sb[j]-sb[indexj])+f[i-1][indexj], (sa[i]-sa[indexi[j]])*b[j]+f[indexi[j]][j-1]); } } printf(“%i\n”, f[n][m]); 6. Test Data The important parameters of the test data are L1 and L2: the lengths of the two sequences. Here is a table containing the various values for L1 and L2 on the different test cases: Test L1 L2 01 20 20 02 110 80 03 200 130 04 400 80 05 1000 333 06 510 910 07 1200 1400 08 700 1800 09 1998 1370 10 2000 1999