Optimal Strategy for a Game | Set 3

Consider a row of n coins of values v1 . . . vn, where n is even. We play a game against an opponent by alternating turns. In each turn, a player selects either the first or last coin from the row, removes it from the row permanently, and receives the value of the coin. Determine the maximum possible amount of money we can definitely win if we move first.

Also, print the sequence of moves in the optimal game. As many sequences of moves may lead to the optimal answer, you may print any valid sequence.

Before the sequence, the part is already discussed in these articles.

  1. Optimal Strategy for a Game
  2. Optimal Strategy for a Game | Set-2

Examples:

Input: 10 80 90 30
Output: 110 RRRL



Explanation:
P1 picks 30, P2 picks 90, P1 picks 80 and finally P2 picks 10.
Score obtained by P1 is 80 + 30 = 110
Max possible score for player 1 is : 110
Optimal game moves are : RRRL

Input: 10 100 10
Output: 20 RRL

Approach:
In each turn(except the last) a player will have two options either to pick the bag on the left or to pick the bag on the right of the row. Our focus is to evaluate the maximum score attainable by P1, let it be S. P1 would like to choose the maximum possible score in his turn whereas P2 would like to choose the minimum score possible for P1.

So P1 focuses on maximizing S, while P2 focuses on minimizing S.

Naive Approach:

Below is the implementation of the above approach:

filter_none

edit
close

play_arrow

link
brightness_4
code

// C++ implementation
#include <bits/stdc++.h>
using namespace std;
  
// Calculates the maximum score
// possible for P1 If only the
// bags from beg to ed were available
pair<int, string> maxScore(vector<int> money,
                           int beg,
                           int ed)
{
    // Length of the game
    int totalTurns = money.size();
  
    // Which turn is being played
    int turnsTillNow = beg
                       + ((totalTurns - 1) - ed);
  
    // Base case i.e last turn
    if (beg == ed) {
  
        // if it is P1's turn
        if (turnsTillNow % 2 == 0)
            return { money[beg], "L" };
  
        // if P2's turn
        else
            return { 0, "L" };
    }
  
    // Player picks money from
    // the left end
    pair<int, string> scoreOne
        = maxScore(money,
                   beg + 1,
                   ed);
  
    // Player picks money from
    // the right end
    pair<int, string> scoreTwo
        = maxScore(money, beg, ed - 1);
  
    if (turnsTillNow % 2 == 0) {
  
        // if it is player 1's turn then
        // current picked score added to
        // his total. choose maximum of
        // the two scores as P1 tries to
        // maximize his score.
        if (money[beg] + scoreOne.first
            > money[ed] + scoreTwo.first) {
            return { money[beg] + scoreOne.first,
                     "L" + scoreOne.second };
        }
        else
            return { money[ed] + scoreTwo.first,
                     "R" + scoreTwo.second };
    }
  
    // if player 2's turn don't add
    // current picked bag score to
    // total. choose minimum of the
    // two scores as P2 tries to
    // minimize P1's score.
    else {
  
        if (scoreOne.first < scoreTwo.first)
            return { scoreOne.first,
                     "L" + scoreOne.second };
        else
            return { scoreTwo.first,
                     "R" + scoreTwo.second };
    }
}
  
// Driver Code
int main()
{
    // Input array
    int ar[] = { 10, 80, 90, 30 };
  
    int arraySize = sizeof(ar) / sizeof(int);
  
    vector<int> bags(ar, ar + arraySize);
  
    // Function Calling
    pair<int, string> ans
        = maxScore(bags,
                   0,
                   bags.size() - 1);
    cout << ans.first << " " << ans.second << endl;
    return 0;
}
chevron_right

Output:
110 RRRL

The time complexity of the above approach is exponential.

Optimal Approach:
We can solve this problem by using dynamic programming in time and space complexity.

  • To keep the track of the moves that take place at a given state we keep an additional boolean matrix which allows us to reconstruct the entire game moves that lead to the maximum score.
  • The matrix leftBag(i, j) represents a state in which only the bag from i to j are present. leftBag(i, j) is 1 if it is optimal to pick the leftBag otherwise it is 0.
  • The function getMoves uses this matrix to reconstruct the correct moves.
  • Below is the implementation of the above approach:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    // C++ implementation
    #include <bits/stdc++.h>
    #define maxSize 3000
      
    using namespace std;
      
    // dp(i, j) is the best
    // score possible if only
    // the bags from i, j were
    // present.
      
    int dp[maxSize][maxSize];
      
    // leftBag(i, j) is 1 if in the
    // optimal game the player picks
    // the leftmost bag when only the
    // bags from i to j are present.
      
    bool leftBag[maxSize][maxSize];
      
    // Function to calculate the maximum
    // value
    int maxScore(vector<int> money)
    {
        // we will fill the dp table
        // in a bottom-up manner. fill
        // all states that represent
        // lesser number of bags before
        // filling states that represent
        // higher number of bags.
        // we start from states dp(i, i)
        // as these are the base case of
        // our DP solution.
        int n = money.size();
        int totalTurns = n;
      
        // turn = 1 if it is player 1's
        // turn else 0. Who gets to pick
        // the last bag(bottom-up so we
        // start from last turn)
        bool turn
            = (totalTurns % 2 == 0) ? 0 : 1;
      
        // if bag is picked by P1 add it
        // to the ans else 0 contribution
        // to score.
        for (int i = 0; i < n; i++) {
            dp[i][i] = turn ? money[i] : 0;
            leftBag[i][i] = 1;
        }
      
        // 2nd last bag is picked by
        // the other player.
        turn = !turn;
      
        // sz represents the size
        // or number of bags in
        // the state dp(i, i+sz)
        int sz = 1;
      
        while (sz < n) {
      
            for (int i = 0; i + sz < n; i++) {
                int scoreOne = dp[i + 1][i + sz];
                int scoreTwo = dp[i][i + sz - 1];
      
                // First player
                if (turn) {
                    dp[i][i + sz]
                        = max(money[i] + scoreOne,
                              money[i + sz] + scoreTwo);
      
                    // if leftBag has more profit
                    if (money[i] + scoreOne
                        > money[i + sz] + scoreTwo)
                        leftBag[i][i + sz] = 1;
      
                    else
                        leftBag[i][i + sz] = 0;
                }
      
                // second player
                else {
                    dp[i][i + sz]
                        = min(scoreOne,
                              scoreTwo);
      
                    if (scoreOne < scoreTwo)
                        leftBag[i][i + sz] = 1;
      
                    else
                        leftBag[i][i + sz] = 0;
                }
            }
      
            // Give turn to the
            // other player.
            turn = !turn;
      
            // Now fill states
            // with more bags.
            sz++;
        }
      
        return dp[0][n - 1];
    }
      
    // Using the leftBag matrix,
    // generate the actual game
    // moves that lead to the score.
    string getMoves(int n)
    {
        string moves;
        int left = 0, right = n - 1;
      
        while (left <= right) {
      
            // if the bag is picked from left
            if (leftBag[left][right]) {
                moves.push_back('L');
                left++;
            }
      
            else {
                moves.push_back('R');
                right--;
            }
        }
        return moves;
    }
      
    // Driver Code
    int main()
    {
        int ar[] = { 10, 80, 90, 30 };
        int arraySize = sizeof(ar) / sizeof(int);
      
        vector<int> bags(ar, ar + arraySize);
        int ans = maxScore(bags);
      
        cout << ans << " " << getMoves(bags.size());
        return 0;
    }
    chevron_right
    
    
    Output:
    110 RRRL
    

    Time and space complexities of this approach are .

    Attention reader! Don’t stop learning now. Get hold of all the important DSA concepts with the DSA Self Paced Course at a student-friendly price and become industry ready.





    Final year BTech IT student at DTU, Upcoming Technology Analyst at Morgan Stanley

    If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

    Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


    Article Tags :