📜  游戏的最优策略|设置 3

📅  最后修改于: 2021-09-22 10:33:51             🧑  作者: Mango

考虑一排 n 个硬币,价值为 v1 。 . . vn,其中 n 是偶数。我们通过交替轮流与对手进行游戏。在每一轮中,玩家从该行中选择第一个或最后一个硬币,将其从该行中永久移除,并获得该硬币的价值。确定如果我们先行动,我们肯定能赢得的最大可能金额。
此外,打印最佳游戏中的移动顺序。由于许多移动序列可能会导致最佳答案,因此您可以打印任何有效序列。

在序列之前,这些文章中已经讨论了该部分。

  1. 游戏的最优策略
  2. 游戏的最优策略|组 2

例子:

方法:
在每一回合(除了最后一回合),玩家将有两个选择,要么选择左边的袋子,要么选择该行右边的袋子。我们的重点是评估 P1 可获得的最大分数,让它成为 S。 P1 想在轮到他时选择最大可能的分数,而 P2 想为 P1 选择可能的最低分数。
所以P1专注于最大化S,而P2专注于最小化S。

天真的方法:

  • 我们可以为该问题编写一个蛮力递归解决方案,模拟游戏的所有可能性,并找到在给定约束下最大的分数。
  • 函数maxScore 返回玩家 1 的最大可能得分以及在游戏过程中发生的移动。
  • 由于该函数需要返回最大可能分数和导致该分数的移动,我们使用一对 integer 和字符串。
  • 代表游戏动作的字符串由字符“L”和“R”组成,分别表示最左边或最右边的袋子被选中。

下面是上述方法的实现:

C++
// C++ implementation
#include 
using namespace std;
 
// Calculates the maximum score
// possible for P1 If only the
// bags from beg to ed were available
pair maxScore(vector 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 scoreOne
        = maxScore(money,
                   beg + 1,
                   ed);
 
    // Player picks money from
    // the right end
    pair 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 bags(ar, ar + arraySize);
 
    // Function Calling
    pair ans
        = maxScore(bags,
                   0,
                   bags.size() - 1);
    cout << ans.first << " " << ans.second << endl;
    return 0;
}


Python3
# Python3 implementation
 
# Calculates the maximum score
# possible for P1 If only the
# bags from beg to ed were available
def maxScore( money, beg, ed):
     
    # Length of the game
    totalTurns = len(money)
     
    # Which turn is being played
    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
    scoreOne = maxScore(money, beg + 1, ed)
     
    # Player picks money from
    # the right end
    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[0] >
             money[ed] + scoreTwo[0]):
            return [money[beg] + scoreOne[0],
                           "L" + scoreOne[1]]
        else:
            return [money[ed] + scoreTwo[0],
                          "R" + scoreTwo[1]]
             
    # 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[0] < scoreTwo[0]):
            return[scoreOne[0], "L" + scoreOne[1]]
        else:
            return[scoreTwo[0], "R" + scoreTwo[1]]
 
# Driver Code
 
# Input array
ar =  [ 10, 80, 90, 30 ]
arraySize = len(ar)
bags = ar
 
# Function Calling
ans = maxScore(bags, 0, arraySize - 1)
print(ans[0], ans[1])
 
# This code is contributed by shivani


Javascript


C++
// C++ implementation
#include 
#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 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 bags(ar, ar + arraySize);
    int ans = maxScore(bags);
 
    cout << ans << " " << getMoves(bags.size());
    return 0;
}


输出:
110 RRRL

上述方法的时间复杂度是指数级的。

最佳方法:
我们可以通过使用动态规划来解决这个问题O(n^{2})     时间和空间复杂度。

  • 如果我们为从某个开头i到某个结尾j 的所有包存储最佳答案,那么最多可以有n^{2}     这样不同的子问题。
  • dp(i, j)表示如果行中唯一剩余的袋子从i开始并在j结束,则P1可以获得的最大分数。然后保持以下内容:
    • 如果轮到P1
      • dp(i, j) = 包i + dp(i+1, j)或包j + dp(i, j-1)得分的最大值。
    • 如果轮到P2
      • dp(i, j) = dp(i+1, j)dp(i, j-1) 的最小值。
        由于当前包的得分为 P2,我们不将其添加到dp(i, j)
  • 为了跟踪在给定状态下发生的移动,我们保留了一个额外的布尔矩阵,它允许我们重建导致最大分数的整个游戏移动。
  • 矩阵 leftBag(i, j) 表示只存在从ij的包的状态。如果选择 leftBag 是最佳选择, leftBag(i, j) 为1 ,否则为0
  • 函数getMoves 使用这个矩阵来重建正确的移动。

下面是上述方法的实现:

C++

// C++ implementation
#include 
#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 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 bags(ar, ar + arraySize);
    int ans = maxScore(bags);
 
    cout << ans << " " << getMoves(bags.size());
    return 0;
}
输出:
110 RRRL

这种方法的时间和空间复杂度是O(N^{2})     .

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程