题目链接: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]);
}
}