让我们玩游戏:
连续有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个硬币。我希望我说明了足够的问题。你同意这很有意思吗? :)有人可以帮忙吗?
答案 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]的最佳游戏。
我们所做的只是依次考虑每一步:
所以最好的举措是只采取第一个堆栈。