洛谷 P3004 [USACO10DEC]Treasure Chest S/CSES 1097

洛谷 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 imes 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一个点......
洛谷 P3004 [USACO10DEC]Treasure Chest S/CSES 1097

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;
}