游戏
题目描述:
N个数,A和B从左往右依次拿数。首先A先手,他可以拿走一个或两个数。如果前一个人拿走了K个数,下一个人能拿走K或者K+1个数,如果剩下的数不够拿,那么就舍弃剩下的数。每个人的策略是尽量让自己比别人拿的数的总和更大。问A会比B多拿多少。
输入格式:
第一行一个整数T代表组数。
每组数据的第一行一个整数N表示数的个数。
接下来N个数。
输出格式:
输出一个整数,表示A比B多拿了多少。
数据范围:
对于30%的数据:n<=100;
对于70%的数据:N<=2000;
对于100%的数据:1<=N<=20000;-100000<=num[i]<=100000;T<=10。
解析:
动态规划。
大致思路是:令dp[i][j]表示已经拿走了i个数,上一次拿了j个数,按照最优策略最终会比另一个人多拿多少。可得到状态转移方程为:dp[i][j]=sum[i]-sum[i-j]+max(dp[i-j][j],dp[i-j][j+1]); //sum数组记录前缀和
值得注意的是这里需要将这串数反过来运算,原因是这样的:
我们知道dp[i][j]是由dp[i-j][j]和dp[i-j][j-1]转换得来的,比如我们知道了B最后一次拿的最优解可能是dp[i-j][j],现在轮到A拿,但是对A来说的最优解是dp[i-j][j-1],对B来说不是最优的,也就是说正着来算可能会有后效性,所以不如倒过来算,这样结果一定是最优的。
代码:
#include <bits/stdc++.h>
using namespace std;
const int Max=20010;;
int t,n;
int sum[Max],dp[Max][200],num[Max];
inline int get_int()
{
int x=0,f=1;
char c;
for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
if(c=='-') {f=-1;c=getchar();}
for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
return x*f;
}
inline void clean()
{
memset(sum,0,sizeof(sum));
memset(dp,0,sizeof(dp));
memset(num,0,sizeof(num));
}
int main()
{
// freopen("game.in","r",stdin);
//frepoen("game.out","w",stdout);
t=get_int();
while(t--)
{
clean();
n=get_int();
for(register int i=1;i<=n;i++) num[i]=get_int();
reverse(num+1,num+n+1); //将整串数倒过来,O(1)的复杂度
for(register int i=1;i<=n;i++) sum[i]=sum[i-1]+num[i];
for(register int i=1;i<=n;i++)
for(register int k=1;k<=200;k++)
if(k<=i)
{
int pos=i-k;
dp[i][k]=sum[i]-sum[pos]-max(dp[pos][k],dp[pos][k+1]);
}
cout<<max(dp[n][1],dp[n][2])<<"\n";
}
return 0;
}