Download View solution

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
Transcript
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