A lot of people at Facebook like to play Starcraft II™. Some of them have made a custom game using the Starcraft II™ map editor. In this game, you play as the noble Protoss defending your adopted homeworld of Shakuras from a massive Zerg army. You must do as much damage to the Zerg as possible before getting overwhelmed. You can only build two types of units, shield generators and warriors. Shield generators do no damage, but your army survives for one second per shield generator that you build. Warriors do one damage every second. Your army is instantly overrun after your shield generators expire. How many shield generators and how many warriors should you build to inflict the maximum amount of damage on the Zerg before your army is overrun? Because the Protoss value bravery, if there is more than one solution you should return the one that uses the most warriors.

6 Answers
6

Here's a solution whose complexity is O(W). Let g be the number of generators we build, and similarly let w be the number of warriors we build (and G, W be the corresponding prices per unit).

We note that we want to maximize w*g subject to w*W + g*G <= M.

First, we'll get rid of one of the variables. Note that if we choose a value for g, then obviously we should buy as many warriors as possible with the remaining amount of money M - g*G. In other words, w = floor((M-g*G)/W).

Now, the problem is to maximize g*floor((M-g*G)/W) subject to 0 <= g <= floor(M/G). We want to get rid of the floor, so let's consider W distinct cases. Let's write g = W*k + r, where 0 <= r < W is the remainder when dividing g by W.

The idea is now to fix r, and insert the expression for g and then let k be the variable in the equation. We'll get the following quadratic equation in k:

This is a quadratic equation which goes to negative infinity when x goes to infinity or negative infinity so it has a global maximum at k = -B/(2A). To find the maximum value for legal values of k, we'll try the minimum legal value of k, the maximum legal value of k and the two nearest integer points of the real maximum if they are within the legal range.

The overall maximum for all values of r is the one we are seeking. Since there are W values for r, and it takes O(1) to compute the maximum for a fixed value, the overall time is O(W).

In the last step, finding the two nearest valid integer points does not actually seem possible in O(1) time. You need both that k is an integer, and also that (M-r*G)/W is an integer. However, I think it suffices to take the fractional optimum plus or minus 100. So you can get an O(W^2) time solution.
–
daveagpJan 11 '12 at 15:52

Also, as an aside, the sample output is wrong. The first test case has G=15, W=10, M=658931394179. They propose that the optimal solution has value g=21964393379, giving w=32946549349 and objective value gw=723650970382072360271. However, a better solution (and what I think is optimal) is to take g=21964379805, giving w=32946569710 and objective value gw=723650970382348706550 (an improvement of 276346279).
–
daveagpJan 11 '12 at 15:56

If you build g generators, you can build at most (M - g × G)/W warriors, and do g × (M - g × G)/W damage.

This function has a maximum at g = M / (2 G), which results in M2 / (4 GW) damage.

Summary:

Build M / (2 G) shield generators.

Build M / (2 G) warriors.

Do M2 / (4 GW) damage.

Since you can only build integer amounts of any of the two units, this reduces to the optimization problem:

maximizeg × wwith respect tog × G + w × W &leq; Mandg, w &in; ℤ+

The general problem of Integer Programming is NP-complete, so the best algorithm for this is to check all integer values close to the real-valued solution above.

If you find some pair (gi, wi), with total damage di, you only have to check values where gj × wj &geq; di. This and the original condition W × w + G × g &leq; M constrains the search-space with each item found.

The problem with this is that it only solves the continuous case. And with the generalized costs you can't justs use the closest discrete values.
–
CodesInChaosJan 15 '11 at 20:21

@CodeInChaos: That's probably not an insurmountable problem in this case. Once you have a good point (a point near the continuous solution) you can find the continuous space that deals at least that much damage and search for discrete points in that region, which for this problem should be small.
–
CharlesJan 16 '11 at 4:43

In your code, how did you come up with the range [minW, maxW]? And once you fix w, shouldn't you fix g to be maxG? Why did you have a nested loop ranging from minG to maxG?
–
Wei HuJan 18 '11 at 8:47

1

minW/maxW is value of w in the two solutions to the equation system { G*g + W*w = M; m * g = bestScore }. That is, the minimum and maximum value of w for configurations that do at least bestScore damage.
–
Markus JarderotJan 19 '11 at 7:16

This assumed W and G were the counts and the cost of each was equal to 1. So it's obsolete with the updated question.

Damage = LifeTime*DamagePerSecond = W * G

So you need to maximize W*G with the constraint G+W <= M. Since both Generators and Warriors are always good we can use G+W = M.

Thus the function we want to maximize becomes W*(M-W).
Now we set the derivative = 0:
M-2W=0
W = M/2

But since we need the solution to the discrete case(You can't have x.5 warriors and x.5 generators) we use the values closest to the continuous solution(this is optimal due to the properties of a parabel).

If M is even than the continuous solution is identical to the discrete solution. If M is odd then we have two closest solutions, one with one warrior more than generators, and one the other way round. And the OP said we should choose more warriors.

So the final solution is:
G = W = M/2 for even M
and G+1 = W = (M+1)/2 for odd M.

The assumption is that you already get a high value of damage when the number of shields equals 1 (cannot equal zero or no damage will be done) and the number of warriors equals (m-g)/w. Iterating up should (again an assumption) reach the point of compromise between the number of shields and warriors where damage is maximized. This is handled by the bestDamage > calc branch.

There is almost likely a flaw in this reasoning and it'd be preferable to understand the maths behind the problem. As I haven't practised mathematics for a while I'll just guess that this requires deriving a function.

Interesting discussion in the blog post, but the comments show they still haven't come to a correct solution. Your solution is correct, but will be extremely slow given the specified constraints.
–
marcogJan 16 '11 at 10:29

Yes, I gave it a thorough read and it limits itself to exploring a representation of the problem. Ideally, there would be a second equation to intersect with the first one.
–
James PoulsonJan 16 '11 at 11:01

Since I solved this last night, I thought I'd post my C++ solution. The algorithm starts with an initial guess, located at the global maximum of the continuous case. Then it searches 'little' to the left/right of the initial guess, terminating early when continuous case dips below an already established maximum. Interestingly, the 5 example answers posted by the FB contained 3 wrong answers: