洛谷 P3004 [USACO10DEC]Treasure Chest S/CSES 1097
CSES 1097数据较弱,二位的动归数组也能过。
Description
一个长度为n的数列,A、B 两人轮流从数列头或尾取数。如果两人足够聪明,问A先手时可以获得的最大分数。
Limit
\(1≤n≤5×10 ^3 ,1 \leq c_i \leq 5 \times 10^3\)。
空间仅有64MB。
Solution
Solution 1 90pts
初看这题,很容易想到区间DP!
设 \(f[i][j]\) 表示考虑 \((i,j)\) 这段区间,A能取到的最大价值。
先暴力预处理一个前缀和数组 sum,保存每个区间的区间和,表示 A 与 B 一起取这一段区间会得到 \(sum[j] - sum[i - 1]\) 的分数。
又因为A要让B拿到的分尽量小,每次只能从前面取或者后面取,那肯定要把小的给对手,大的我取走(
不难写出转移方程:\(f[i][j] = sum[i][j] - min(f[i+1][j], f[i][j-1])\)。
最后答案即为 \(f[1][n]\),代码也很好写~
时间复杂度:\(O(n^2)\)。
空间复杂度:\(O(n^2)\)。
Code 1
// by pjx Jul.
signed main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
f[i][i] = a[i];//初始值:长度为1的区间价值就是他本身(
sum[i] = sum[i - 1] + a[i];
}
for(int i = n; i >= 1; i--)
{
for(int j = i + 1; j <= n; j++)
{
f[i][j] = sum[j] - sum[i - 1] - min(f[i + 1][j], f[i][j - 1]);
}
}
cout << f[1][n];
}
但是,交上去:嗯?MLE一个点......
Solution 2 \(100pts\)
由于两维的DP数组空间过大,考虑压掉一维(
看到每次转移时只跟 \(i+1\)、\(i-1\) 有关系,考虑滚动数组,设 \(f[i][j % 2]\) 表示考虑 \((i,j)\) 这段区间,A能取到的最大价值。
转移方程改动一下就能过了。
空间复杂度降至 \(O(n)\)。
Code 2:
// by pjx Aug.
//滚动数组优化
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#define REP(i, x, y) for(register int i = x; i < y; i++)
#define rep(i, x, y) for(register int i = x; i <= y; i++)
#define PER(i, x, y) for(register int i = x; i > y; i--)
#define per(i, x, y) for(register int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 5005;
int n, c[N];
int f[N][2], sum[N];
int main()
{
cin >> n;
rep(i, 1, n)
{
cin >> c[i];
sum[i] = sum[i - 1] + c[i];
}
for(int len = 1; len <= n; len++)
{
for(int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
f[l][len % 2] = sum[r] - sum[l - 1] - min(f[l + 1][(len - 1) % 2], f[l][(len - 1) % 2]);
}
}
cout << f[1][n % 2];
return 0;
}