Topcoder SRM 343, D2, 1000-point problem

James S. Plank
November, 2012

Problem description:

The driver for this is in Mafia-Main.cpp, which includes Mafia.cpp and compiles the example cases.

The problem description is fairly long, but go through example 0 so that you understand the basic mechanics.

Using Dynamic Programming -- Spotting the Recursion

As with many approaches that involve dynamic programming, the key is to spot the recursion. The first approach I'd take in this problem is a very brain-dead recursive approach. If you start during the day, there's no choice -- you simply find the most guilty player and move on. However, during the evening, you try all of the choices for removing players, by removing a player and then calling the game recursively.

How do you "call the game recursively?" Well, the first thing you could do is create new guilt and responses vectors, and call play() recursively on those. However, that's a lot of work, and as you'll see later, it's harder to memoize. Instead, let's represent the state of a game by a string, which has N characters, one for each player in the game. The character for each player will be:

Think to yourself -- if I have this string, plus the original guilt and responses vectors, can I calculate every survivor's guilt at this point in the game, regardless of the order in which people were removed from the game? The answer to that is "Yes": Suppose player i is a survivor and players j and k have been removed by the Mafia. Then player i's guilt is going to be equal to:

guilt[i] + responses[j][i] + responses[k][i]

That's assuming of course, that you've converted responses to vectors of integers instead of those stupid strings. This string that represents the game's state is important, because it gives us something on which to memoize.

Let's work through Example 0 with the string. The first thing we do is add a few variables to our class. The first is a vector of vectors of integers called R, which converts responses to integers. In Example 0, this will be:

R = { {  1,  4,  3, -2 },
      { -2,  1,  4,  3 },
      {  3, -2,  1,  4 },
      {  4,  3, -2,  1 } }
The second is G, which is simply a copy of the original guilt, and the third is Me, which is a copy of playerIndex.

Then we add a method int RPlay(string state), which returns the maximum number of rounds that we can play given the string that represents the game state. The first call we'll make is the initial one: RPlay("0000"). Now, let's go through all the recursive calls that will be made:

This example didn't feature any duplicate calls, but you can see how they might arise. Therefore, you memoize on the key (use a map).

Have fun!