题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1430
给定一个长为n的整数序列,由A和B轮流取数(A先取)。每个人可从序列的左端或右端取若干个数(至少一个),但不能两端都取。所有数都被取走后,两人分别统计所取数的和作为各自的得分。假设A和B都足够聪明,都使自己得分尽量高,求A的最终得分。
思路:
时限3s,显然是要一个常数小的O(Tn2)的方法。
由于每次A和B都只可以在左右两端取数,所以取完后剩余的一定是一个连续的子区间。
正序做似乎不是很好做,考虑倒序,将问题转化成一个区间问题。
先考虑最暴力的方法,若先手取时剩余区间为[l,r],那么后手一定是取了[x,r]或[l,x](l<x<r),然后先手取[l,x−1]或[x+1,r]。
设dp[i][j]表示先手取成[l,r]的最优答案,那么这个答案一定由后手的最劣方案转移而来。因为若后手取后的区间和为s′,区间[l,r]的和为s,那么先手取的和为s−s′。我们要让s−s′尽量大,那么就让s′尽量小即可。即后手的最劣方案。
所以转移方程就是
dp[i][j]=sum[j]−sum[i−1]−min(0,dp[i][i],dp[i][i+1],...,dp[i][j−1],dp[j][j],dp[j−1][j],...,dp[i+1][j])
其中sum[i]为a[i]的前缀和,sum[j]−sum[i−1]即∑k=ija[k];加上0的原因是因为先手可以一次把所有选完,那么后手就不能选,即选的和为0。
这样单次询问的复杂度是O(n3)的。
看到方程中有min,min需要O(n)的复杂度,但是dp中的min一般都是可以进行转移的。所以可以通过转移min来去掉这个O(n)。
设f[i][j]=min(dp[i][i],dp[i][i+1],...,dp[i][j]),g[i][j]=min(dp[j][j],dp[j−1][j],...dp[i][j])。
方程就变成了
dp[i][j]=sum[j]−sum[i−1]−min(0,f[i+1][j],g[i][j−1])
f,g的维护都是最最最最基础的了。显然有f[i][j]=min(f[i+1][j],dp[i][i]),g同理。
这样单次询问的复杂度就降到了O(n2)。总时间复杂度就是O(Tn2)。而且区间dp的常数好像还是21?
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int T,n,a[N],sum[N],dp[N][N],f[N][N],g[N][N];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
dp[i][i]=f[i][i]=g[i][i]=a[i];
}
for (int i=n-1;i>=1;i--)
for (int j=i+1;j<=n;j++)
{
dp[i][j]=sum[j]-sum[i-1]-min(0,min(f[i+1][j],g[i][j-1]));
g[i][j]=min(g[i][j-1],dp[i][j]);
f[i][j]=min(f[i+1][j],dp[i][j]);
}
printf("%d
",dp[1][n]);
}
return 0;
}