📜  游戏的最佳策略|套装3

📅  最后修改于: 2021-04-29 05:55:14             🧑  作者: Mango

考虑一行n个硬币,其值为v1。 。 。 vn,其中n为偶数。我们通过交替轮流与对手进行比赛。在每个回合中,玩家从该行中选择第一个或最后一个硬币,将其从该行中永久删除,然后接收硬币的价值。确定如果我们先走,我们绝对可以赢得的最大金额。

另外,在最佳游戏中打印移动顺序。由于许多移动顺序可能会导致最佳答案,因此您可以打印任何有效的顺序。

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

  1. 游戏的最佳策略
  2. 游戏的最佳策略|套装2

例子:

方法:
在每个回合中(除了最后一个回合),玩家将有两个选择,要么是在行的左边捡起袋子,要么是在行的右边捡起袋子。我们的重点是评估P1可以达到的最高得分,以S为准。P1希望依次选择最高得分,而P2则希望为P1选择最低得分。

因此,P1专注于使S最大化,而P2专注于使S最小化。

天真的方法:

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

下面是上述方法的实现:

// 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;
}
输出:
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++ 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})