题目链接:HDU 6199 : gems gems gems

题目大意:两个人轮流取物品,若第$i$ 次拿了$k$ 个,则第$i+1$ 次可以拿$k$ 或$k+1$ 个。两人都想拿到值更大的物品。第一次能拿$1$ 或$2$ 个物品,问第一个人拿了多少。

DP

在不考虑$32M$ 空间限制的情况下,显然我们可以定义两个数组:

  • $dp1[i][j]$ 表示第二个人前一次拿了$j$ ,第一个人当前从$i$ 个物品开始拿的最大价值
  • $dp2[i][j]$ 表示第一个人前一次拿了$j$ ,第二个人当前从$i$ 个物品开始拿的最大价值

然后写两个dfs函数记忆化搜索一下即可。

然而这题不仅卡空间,还卡时间(递归常数较大,扣完空间还会超时),于是只能用递推搞。

原先的状态不适合递推,故重新思考状态。

由于对两个人来说,每个人都想取得最大价值,原先的两种状态可以合并为一种状态:

  • $dp[i][j]$ 表示前一个人前一次拿了$j$ ,当前从$i$ 个物品开始拿的最大价值

复杂度$O(n^{\frac{3}{2}})$ 。

代码如下:

#include <cstdio>
#include <cmath>
#define Max(a,b) (a>b?a:b)
using namespace std;
const int N=20000+5;
const int inf=1e9;
int T,n,a[N],dp[N][205],pre[N];
int query(int l,int r){
    return pre[r]-pre[l-1];
}
int main(void){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        if(n==1){
            printf("%d\n",a[1]);
            continue;
        }
        for(int i=1;i<=n;++i)pre[i]=pre[i-1]+a[i];
        for(int i=n;i>=1;--i){
            for(int j=(int)((-1+sqrt(1+8*i))/2)-1;j>=0;--j){
                if(i+j>n)dp[i][j]=0;
                else if(i+j==n)dp[i][j]=query(i,i+j);
                else if(i+j+1==n)dp[i][j]=Max(-dp[i+j+1][j]+query(i,i+j),query(i,i+j+1));
                else dp[i][j]=Max(-dp[i+j+1][j]+query(i,i+j),-dp[i+j+2][j+1]+query(i,i+j+1));
            }
        }
        printf("%d\n",dp[1][0]);
    }
}