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