给定n(1e6),a(-5…-1),b(±1e7),c(±1e7),一个长为n的数列(100),现在要将其分成连续的若干段。
每段的价值是
a
x
2
+
b
x
+
c
ax^2+bx+c
ax2+bx+c,其中
x
x
x是这段数字的和。求最大的总价值。
设
d
i
d_i
di表示从
1
1
1到
i
i
i分好的最大价值,
d
0
=
0
d_0=0
d0=0;
d
i
=
m
a
x
{
d
j
+
a
(
s
i
−
s
j
)
2
+
b
(
s
i
−
s
j
)
+
c
}
,
0
<
=
j
<
i
d_i=max\{d_j+a(s_i-s_j)^2+b(s_i-s_j)+c\},0<=j<i
di=max{dj+a(si−sj)2+b(si−sj)+c},0<=j<i,其中
s
i
s_i
si表示数列的前缀和。
展开得,
d
i
=
a
s
i
2
+
b
s
i
+
c
+
m
a
x
{
d
j
+
a
s
j
2
−
b
s
j
−
2
a
s
i
s
j
}
d_i=as_i^2+bs_i+c+max\{d_j+as_j^2-bs_j-2as_is_j\}
di=asi2+bsi+c+max{dj+asj2−bsj−2asisj}
设
j
<
k
<
i
j<k<i
j<k<i,那么“从
k
k
k转移优于从
j
j
j转移”等价于
d
k
+
a
s
k
2
−
b
s
k
−
2
a
s
i
s
k
>
d
j
+
a
s
j
2
−
b
s
j
−
2
a
s
i
s
j
d_k+as_k^2-bs_k-2as_is_k>d_j+as_j^2-bs_j-2as_is_j
dk+ask2−bsk−2asisk>dj+asj2−bsj−2asisj
设
y
i
=
d
i
+
a
s
i
2
−
b
s
i
,
x
i
=
2
a
s
i
y_i=d_i+as_i^2-bs_i,x_i=2as_i
yi=di+asi2−bsi,xi=2asi,因为
a
<
0
a<0
a<0,可以整理得:
y
k
−
y
j
x
k
−
x
j
<
s
i
\frac{y_k-y_j}{x_k-x_j}<s_i
xk−xjyk−yj<si
由斜率优化原理(参见【笔记】斜率DP),斜率小于 s i s_i si时 k k k比 j j j优对应于需要维护一个下凸包,而且 s i s_i si单调递增,可以使用双端队列维护下凸包。
我WA的好惨。。。
主要原因是
x
k
−
x
j
=
2
a
(
s
k
−
s
i
)
x_k-x_j=2a(s_k-s_i)
xk−xj=2a(sk−si)是一个负数,好多分析过程就不成立了。
考虑不把
a
a
a算在
x
i
x_i
xi里,即
x
i
=
2
s
i
x_i=2s_i
xi=2si,那么斜率式就是:
y
k
−
y
j
x
k
−
x
j
>
a
∗
s
i
\frac{y_k-y_j}{x_k-x_j}>a*s_i
xk−xjyk−yj>a∗si
此时不管目标斜率是多少,我都应当维护一个上凸包,即相邻点的斜率单调递减。
然后因为目标斜率也是单调递减的,也可以用双端队列来维护。
总结:
- 尽量把分母之差 x k − x j x_k-x_j xk−xj设为大于0的数,不然会很难做。
- 下凸包( k j k < s i k_{jk}<s_i kjk<si,相邻斜率递增)对应目标斜率 s i s_i si递增,上凸包( k j k > s i k_{jk}>s_i kjk>si,相邻斜率递减)对应目标斜率 s i s_i si递减,满足这两个条件之一时就可以使用双端队列维护凸包。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 1000016, MOD = 1000000007;
ll n, A, B, C;
ll sum[M], dp[M];
int q[M], l, r;
inline ll suby(int j, int k) //计算y[k]-y[j]
{
ll yj = dp[j] + A*sum[j]*sum[j] - B*sum[j];
ll yk = dp[k] + A*sum[k]*sum[k] - B*sum[k];
return yk-yj;
}
inline ll subx(int j, int k) //计算x[k]-x[j]
{
return 2*(sum[k]-sum[j]);
}
inline ll cal_dp(int i, int j) //计算从j转移到i的dp值
{
return dp[j] + A*(sum[i]-sum[j])*(sum[i]-sum[j]) + B*(sum[i]-sum[j]) + C;
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
n = read(), A = read(), B = read(), C = read();
for(int i=1; i<=n; ++i)
sum[i] = sum[i-1] + read();
l = r = 0; q[r++] = 0;
for(int i=1; i<=n; ++i)
{
while(r-l>=2 && suby(q[l],q[l+1])>=sum[i]*A*subx(q[l],q[l+1])) ++l;
dp[i] = cal_dp(i, q[l]);
while(r-l>=2 &&
suby(q[r-2],q[r-1])*subx(q[r-1],i) <= subx(q[r-2],q[r-1])*suby(q[r-1],i)
) --r;
q[r++] = i;
}
cout << dp[n] << endl;
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}