JZOJ 1322. 硬币游戏

来源:互联网 发布:酒店网络机顶盒案例 编辑:程序博客网 时间:2024/06/05 20:17

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] 表示从 ij 的价值和(可以用前缀和维护)。

  • 那么可得转移方程式:

    F[i][j]=max{sum[ik+1][i]+(sum[1][ik]F[ik][k])}

  • 其中:

    1. sum[ik+1][i] 表示本次自己拿走第 i 枚硬币的钱数和;
    2. sum[1][ik]f[ik][k] 表示在剩下的局面中自己还能拿的钱数和;
    3. 1kmin(2k,i)
  • 于是我们将上式化简,得:

    F[i][j]=max{sum[1][i]F[ik][k]} (1kmin(2k,i))

  • 但然而这样的 DP 是 O(N3) 的,极限数据会时间超限,要想办法优化成 O(N2) 的。

  • 继续观察上式,发现两个相邻状态 F[i][j]F[i][j1] 有很多重复的转移,

  • 代入可以发现只有两个状态是有用的,即:当 k=2jk=2j1 时有意义。

  • 那么只转移两个状态,时间复杂度就是 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;}