题意:
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6199
AB两个人玩游戏,n个数,从左到右依次选取。第一个人可以选择1个或者2个数,假设某一轮一个人选择了k个数,那么下一轮另一个人就要选择k个或者k+1个数,如果剩下的数字不够就不能选择,游戏终止。此时比较A-B的大小,问两个人都采取最优策略,都尽可能让自己所获得的数的总和大,最后A-B的最大值是多少。
思路:
真的菜,网络赛时候有正确思路,结果细节没搞清楚,最后愣是没调出来。
dp[i][j][0/1]表示当前这一轮是0(A)或1(B)选择数字,还剩下j个数,马上将要选择i个数的A-B的最大值。然后从后往前倒着推,最后答案就是max(dp[1][n][0], dp[2][n][0])。
状态转移方程为:
dp[i][j][0] = min(dp[i][j-i][1], dp[i+1][j-i][1])+val;
dp[i][j][1] = max(dp[i][j-i][0], dp[i+1][j-i][0])+val;
其中,val为选择的i个数的总和,这里有一点一定要注意,0对应min,而1对应max,不能搞反。因为这是从后往前倒推的,当前是0面对的一定是之前的1所留下来对1最好的局面,也就是min。
恶心的爆内存,要改成滚动数组。
还要注意很多细节问题,详见代码。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 2e9 + 1;
const int MAXN = 2e4 + 10;
int a[MAXN], sum[MAXN], dp[2][MAXN][2];
int main() {
//freopen("in.txt", "r", stdin);
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
sum[i] = sum[i - 1] + a[i];
}
if (n == 1) {
printf("%d\n", a[1]);
continue;
}
for (int i = 199; i >= 1; i--) {
for (int j = i; j <= n; j++) {
int l = n - j, r = n - j + i;
if (j - i < i) {
dp[i % 2][j][0] = sum[r] - sum[l];
dp[i % 2][j][1] = -(sum[r] - sum[l]);
}
else {
dp[i % 2][j][0] = dp[i % 2][j - i][1] + sum[r] - sum[l];
dp[i % 2][j][1] = dp[i % 2][j - i][0] - (sum[r] - sum[l]);
if (i + 1 <= 199 && j - i >= i + 1) {
dp[i % 2][j][0] = min(dp[i % 2][j][0], dp[(i + 1) % 2][j - i][1] + sum[r] - sum[l]);
dp[i % 2][j][1] = max(dp[i % 2][j][1], dp[(i + 1) % 2][j - i][0] - (sum[r] - sum[l]));
}
}
}
}
int ans = max(dp[0][n][0], dp[1][n][0]);
printf("%d\n", ans);
}
return 0;
}