luogu P1430 序列取数
题目大意
给定一个长为 \(n\) 的整数序列 ,由 \(A\) 和 \(B\) 轮流取数(\(A\) 先取)。每个人可从序列的左端或右端取若干个数(至少一个),但不能两端都取。所有数都被取走后,两人分别统计所取数的和作为各自的得分。假设 \(A\) 和 \(B\) 都足够聪明,都使自己得分尽量高,求 \(A\) 的最终得分。
\(T\leq 100, n\leq1000\)
solution
曾老师:“根据题目条件需要啥设啥"
回到题中
题目中所说每个人只能从两端取,所以在序列的两端搞点事情
区间 \(dp\) :考虑大区间如何由小区间转移得到(我方:A, 敌方:B)
\(L[i][j]\) 表示 \([i,j]\) 区间内,\(a[i]\) 必须时选所能获取的最大得分
\(R[i][j]\) 表示 \([i, j]\) 区间内,\(a[j]\) 必须选所能获取的最大得分
\(S[i][j] = \sum_{k = i}^{k = j} a[k]\)
这里的 \(L, R\) 是全局的状态,可以指 A, 也可以指 B
转移:(由于是 \(A\) 和 \(B\) ) 足够聪明,要用到博弈思想
以 \(L[i][j]\) 的转移为例
考虑下一步该怎么走
下一步我方取 \(a[i + 1]\)
\(L[i][j] = a[i] + L[i + 1][j]\)
下一步我方取 \(a[j]\)
因为 \(a[i]\) 必须取,因为 \(a[i]\) , \(a[j]\) 不连续,所以不能同时取走,这个状态显然是不合法的
下一步对手取 \(a[i + 1]\)
\(L[i][j] = a[i] + S[i][j] - L[i + 1][j]\)
\(A\) 的得分等于总分减去对手的得分
下一步对手取 \(a[j]\)
\(L[i][j] = a[i] + S[i][j] - R[i + 1][j]\)
同上
\(R\) 的转移可以根据上面类比过去
初始化
\(L(i, i) = R(i, i) = a[i]\)
转移方程
\(Ans:max(R[1, n],~~L[1, n])\)
复杂度: \(n^2\)
这样就可以取得 100 分的好成绩
考虑优化 区间小技巧
可以把 \(j\) 维滚掉
改一下状态就好了
\(L(i,j)\) 表示此时的区间为 \([i,i+j]\) 且第 \(i\) 个数必须取
\(R(i,j)\) 表示此时的区间为 \([i,i+j]\) 且第 \(i+j\) 个数必须取
转移就把区间表示稍微改一下就好了就成了
答案就成了 \(\max(L(1,n-1),R(1,n-1))\)
code
/*
work by:Ariel
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long
using namespace std;
const int N = 1000 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int L[N], R[N], S[N], T, a[N], n;
int main() {
T = read();
while (T--) {
n = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
S[i] = S[i - 1] + a[i];
L[i] = R[i] = a[i];
}
for (int j = 1; j < n; j++) {
for (int i = 1; i + j <= n; i++) {
R[i] = a[i + j] + max(R[i], S[i + j - 1] - S[i - 1] - max(L[i], R[i]));
L[i] = a[i] + max(L[i + 1], S[i + j] - S[i] - max(L[i + 1], R[i + 1]));
}
}
printf("%d\n", max(L[1], R[1]));
}
return 0;
}