用硬币游戏的算法

时间:2012-11-26 19:40:27

标签: algorithm

让我们玩游戏:

连续有n堆硬币。第i堆栈由d_i币组成。两名球员:Player1,Player2交替进行。玩家轮流只能进行第一次堆叠或最后一次堆叠或两者都进行。当没有剩下的硬币时游戏结束。每个玩家想要在最后拥有尽可能多的硬币。 Player1开始。

我想知道算法(听起来像贪婪的算法)来计算每个玩家在游戏结束时有多少硬币,当两者都达到最佳时。

我不知道如何处理这样的算法。只是猜测策略还是有某种方法来推断它?或者实现算法可能是一个相当奇怪的问题?

示例(从第一个到第n个的堆栈中的硬币):

1,100,1 - 玩家分别拥有2个和100个硬币(不幸的是,第一个玩家只能拿到第一个和最后一个筹码 - 第二个玩家将总是拿着100个硬币堆叠)

<1,1> 1,100,1,1,5-玩家分别有8个和101个硬币(我认为这是在最佳游戏之后 - 首先取5和1,然后第二个拿1来防止玩家1用100个硬币筹码如果玩家1在他的第一步中获得少于6个硬币,他将总是少于8个硬币。

我希望我说明了足够的问题。你同意这很有意思吗? :)有人可以帮忙吗?

2 个答案:

答案 0 :(得分:1)

添加到 @ Peter的动态编程解决方案:

我认为复发看起来有点像下列情况:

考虑硬币堆栈的范围为A[i,..j]
设,dp[i, j]表示Player1可能获得的最高分数。然后,

dp[i, j] = MAX {
                MIN( dp[i+2, j], dp[i+1, j-1], dp[i+2, j-1]) + A[i], //Case when Player2 will try to make the most of it if Player1 picks ith coin.
                MIN( dp[i+1, j-1], dp[i, j-2], dp[i+1, j-2]) + A[j], //Case when Player2 will try to make the most of it if Player1 picks the jth coin.
                MIN( dp[i+2, j-1], dp[i+1, j-2], dp[i+2, j-2]) + A[i] + A[j] // Case when Player2 will try to make the most of it when Player1 picks both the ith and jth coins.
               }

因为只有N ^ 2种可能的游戏状态。它可以通过填写大小为N ^ 2的dp表来实现。

对于C ++粉丝:

#include<iostream>
using namespace std;
int Solve(int A[], int N, int **dp, int i, int j){
        if(dp[i][j] != -1)
                return dp[i][j];
        if(j<i)
                return 0;
        else if(j==i)
                return A[i];
        else if( (j-i) == 1)
                return (A[i] + A[j]);
        else{
                int opt1 = min(Solve(A, N, dp, i+2, j), Solve(A, N, dp, i+1, j-1));
                opt1 = min(opt1, Solve(A, N, dp, i+2, j-1));
                int opt2 = min(Solve(A, N, dp, i+1, j-1), Solve(A, N, dp, i, j-2));
                opt2 = min(opt2, Solve(A, N, dp, i+1, j-2));
                int opt3 = min(Solve(A, N, dp, i+2, j-1), Solve(A, N, dp, i+1, j-2));
                opt3 = min(opt3, Solve(A, N, dp, i+2, j-2));
                int res = max(opt1+A[i], opt2+A[j]);
                res = max(res, opt3+A[i]+A[j]);
                dp[i][j] = res;
                return res;

        }
}
int main(){
        int N;
        int A[N];
        cin >> N;
        for(int i=0; i<N; ++i)
                cin >> A[i];
        int **dp;
        dp = new int* [N];
        for(int i=0; i<N; ++i)
                dp[i] = new int[N];
        for(int i=0; i<N; ++i)
                for(int j=0; j<N; ++j)
                        dp[i][j] = -1;
        Solve(A, N, dp, 0, N-1);
        cout << dp[0][N-1] << endl;
        for(int i=0; i<N; ++i)
                delete [] dp[i];
        delete []dp;
        return 0;
}

另外,正如 @Peter 指出你对第二个例子的分析是错误的。 Player1实际上有一个通过获得102个硬币赢得该游戏的策略。

答案 1 :(得分:0)

如果只存在a到b-1范围内的堆栈,你可以通过解决O(n ^ 2)中的动态编程解决这个问题,解决最佳策略的子问题。

Python代码:

A=[1, 1, 100, 1, 1, 5]
#A=[1, 100, 1]

cache={}
def go(a,b):
    """Find greatest difference between player 1 coins and player 2 coins when choosing from A[a:b]"""
    if a==b: return 0 # no stacks left
    if a==b-1: return A[a] # only one stack left
    key=a,b
    if key in cache:
        return cache[key]

    v=A[a]-go(a+1,b) # taking first stack
    v=max(v,A[b-1]-go(a,b-1)) # taking last stack
    v=max(v,A[a]+A[b-1]-go(a+1,b-1)) # taking both stacks

    cache[key]=v
    return v

v = go(0,len(A))
n=sum(A)
print (n+v)/2,(n-v)/2

这表示你的第二个案例的最佳游戏实际上是第一个人只占最左边的1.从那时起,他可以保证他将获得100,所以他赢了。

(玩家1胜102,玩家2胜7)

有O(n ^ 2)个子游戏,所以这个算法需要时间O(n ^ 2)

子游戏(以及最佳的第一玩家/第二玩家硬币)是:

 [1, 5] 6 0
 [1, 1] 2 0
 [1, 100] 101 0
 [100, 1] 101 0
 [1, 1] 2 0
 [1, 100, 1] 2 100
 [1, 1, 5] 6 1
 [100, 1, 1] 101 1
 [1, 1, 100] 101 1
 [100, 1, 1, 5] 105 2
 [1, 100, 1, 1] 101 2
 [1, 1, 100, 1] 101 2
 [1, 100, 1, 1, 5] 7 101
 [1, 1, 100, 1, 1] 102 2
 [1, 1, 100, 1, 1, 5] 102 7

因此,举例来说,假设我们找到了较小游戏的所有最佳游戏,并希望找到[1,1,100,1,1,5]的最佳游戏。

我们所做的只是依次考虑每一步:

  1. 拿到第一个筹码,这将赢得我们1,离开我们从子问题中知道的游戏[1,100,1,1,5]将赢得101,共计102.
  2. 拿下最后一局,这将赢得我们5,然后离开游戏[1,1,100,1,1],这将只赢得我们额外的2,总共7。
  3. 拿下两个筹码,这将赢得我们6,然后离开游戏[1,100,1,1]再次只让我们赢得2个,如果玩家2最佳播放,总共8个
  4. 所以最好的举措是只采取第一个堆栈。