题目描述
农夫约翰的奶牛喜欢玩硬币游戏,因此他发明了一种称为“Xoinc”的两人硬币游戏。 初始时,一个有N(5 <= N <= 2,000)枚硬币的堆栈放在地上,从堆顶数起的第I枚硬币的币值为C_i (1 <= C_i <= 100,000)。 开始玩游戏时,第一个玩家可以从堆顶拿走一枚或两枚硬币。如果第一个玩家只拿走堆顶的一枚硬币,那么第二个玩家可以拿走随后的一枚或两枚硬币。如果第一个玩家拿走两枚硬币,则第二个玩家可以拿走1,2,3,或4枚硬币。在每一轮中,当前的玩家至少拿走一枚硬币,至多拿走对手上一次所拿硬币数量的两倍。当没有硬币可拿时,游戏结束。 两个玩家都希望拿到最多钱数的硬币。请问,当游戏结束时,第一个玩家最多能拿多少钱呢?
输入
第1行:1个整数N
第2..N+1行:第i+1行包含1个整数C_i
输出
第1行:1个整数表示第1个玩家能拿走的最大钱数。
样例输入
1
3
1
7
2
样例输出
提示
样例说明:第1个玩家先取走第1枚,第2个玩家取第2枚;第1个取走第3,4两枚,第2个玩家取走最后1枚。
不要看到博弈论就心里打怵,其实只是用到一些简单的博弈思想qwq。这道题主要还是dp,那么dp状态怎么描述呢?首先不能确定最后取硬币的是先手的人还是后手的人,所以可以考虑把状态倒着推,最终答案用先手的人第一次取的状态表示。那么就可以把状态描述成f[i][j]表示上一个人取了j个,还剩下i个,当前人在后续步骤中(包括当前这次取的)能获得的最大值,s[i]表示最后i个的价值和(就是后缀和)。假设当前人是第一个人,他取了k个(k<=2*j),第二个人之后能获得的最大值就是f[i-k][k],那么想让第一个人这次及之后能获得最大值,就要让f[i-k][k]尽量小,那么只要枚举k,用s[i]-min{f[i-k][k]}就是第一个人的最大值。但这样枚举k一定会TLE,不过可以发现f[i][j]和f[i][j-1]之间只差了f[i-2*j][2*j]和f[i-2*j+1][2*j-1]两个状态,所以可以把f[i][j]先赋成f[i][j-1]再与这两个状态比较一下就行了,但比较前要先判断i和这两个数(2*j和2*j-1)之间大小关系。但最后答案是什么呢?我们并不知道先手的人第一次取了1个还是2个,那么可以假设前面有一个第0号硬币被第二个人取走了,这样最终结果就是f[n][1].虽然状态是倒着推,但为了方便可以倒着读入正着转移.
最后附上代码.
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int a[2010];
int s[2010];
int f[2010][2010];
int main()
{
scanf("%d",&n);
for(int i=n;i>=1;i--)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
s[i]=s[i-1]+a[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
f[i][j]=f[i][j-1];
if(2*j-1<=i)
{
f[i][j]=max(f[i][j],s[i]-f[i-2*j+1][2*j-1]);
}
if(2*j<=i)
{
f[i][j]=max(f[i][j],s[i]-f[i-2*j][2*j]);
}
}
}
printf("%d",f[n][1]);
}