Description
FJ的奶牛喜欢玩硬币游戏,所以FJ发明了一个新的硬币游戏。一开始有N(5<=N<=2,000)个硬币堆成一叠,从上往下数第i个硬币有一个整数值C_i(1<=C_i<=100,000)。
两个玩家轮流从上倒下取硬币,玩家1先取,可以从上面取1个或2个硬币,下一轮的玩家可以取的硬币数量最少为1个,最多为上一个玩家取的数量的2倍,硬币全部取完比赛结束。
已知玩家2绝顶聪明,会采用最优策略,现在请你帮助玩家1,使得玩家1取得的硬币值的和最大。
Input
第一行输入N
第二至N+1行每行输入一个整数C_i
Output
输出玩家1能获得的最大值。
Sample Input
5
1
3
1
7
2
Sample Output
9
Solution
这题显然是一道博弈题(“绝顶聪明”),但是传统的搜索过不了这么大的极限数据。
观察到目标是求极值,于是我们考虑动态规划。
设 F[i][j] 表示还剩余 i 个硬币、上一次对手选了
j 个硬币的最大获利。再设 sum[i][j] 表示从 i 到
j 的价值和(可以用前缀和维护)。那么可得转移方程式:
F[i][j]=max{sum[i−k+1][i]+(sum[1][i−k]−F[i−k][k])}其中:
- sum[i−k+1][i] 表示本次自己拿走第 i 枚硬币的钱数和;
sum[1][i−k]−f[i−k][k] 表示在剩下的局面中自己还能拿的钱数和;- 1≤k≤min(2∗k,i) 。
于是我们将上式化简,得:
F[i][j]=max{sum[1][i]−F[i−k][k]} (1≤k≤min(2∗k,i))但然而这样的 DP 是 O(N3) 的,极限数据会时间超限,要想办法优化成 O(N2) 的。
继续观察上式,发现两个相邻状态 F[i][j] 和 F[i][j−1] 有很多重复的转移,
代入可以发现只有两个状态是有用的,即:当 k=2∗j 或 k=2∗j−1 时有意义。
那么只转移两个状态,时间复杂度就是 O(N2) ,成功通过本题。
Code
#include<cstdio>
using namespace std;
const int N=2001;
int sum[N],f[N][N];
inline int read()
{
int X=0,w=1; char ch=0;
while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
return X*w;
}
inline int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int n=read();
for(int i=1;i<=n;i++) sum[n-i+1]=read();
for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=n-i+1;j++)
{
/*for(int k=1;k<=2*j && k<=i;k++)
f[i][j]=max(f[i][j],sum[i]-f[i-k][k]); N^3做法 */
f[i][j]=f[i][j-1];
int k=2*j-1;
if(i>=k) f[i][j]=max(f[i][j],sum[i]-f[i-k][k]);
if(i>=++k) f[i][j]=max(f[i][j],sum[i]-f[i-k][k]);
}
printf("%d",f[n][1]);
return 0;
}