F. Bear and Bowling 4
题意:
给一个序列
val
,任选连续的一段
[l,r]
,其价值为
∑rj=lval[j]∗(j−l+1)
,求最大价值 。
简单的说就是可以去掉这个序列的某前缀和某后缀,然后对新得到的
val
求
ans=∑val[i]∗i
,最后求
max(ans)
。
题解:
斜率优化,这个blog前面讲的不错。
花了两天才完全搞懂这个题。
怎么得出来的呢,我们一步一步来。
首先令
sum[i]=∑ij=1val[j]
,令
p[i]=∑ij=1val[j]∗j
。
然后我们就可以表示出任意
[l,r]
的答案,令为
ans[l,r]=p[r]−p[l−1]−(l−1)∗(sum[r]−sum[l−1]),l∈[1,r]
。
注意到里面
l
和
这样可以得到一个
O(n2)
的暴力。
下面来优化。
设有任意三点
k<j<i
,同时假设
ans[j,i]>ans[k,i]
,把上面的
ans
代进去,我得到的结果如下:
过程略,可以自行验证。
为了看起来简单,令
y(x)=(p[x]−x∗sum[x])
,那么上面可以写成如下形式:
最初我推出的是这个式子,然而我用这个去维护却无法ac,后来我把左边完全化为斜率形式:
显然 g(j,k) 可以看做 j 和
前面我们假设 j>k 并且 ans[j,i]>ans[k,i] 结果得到了这个式子。
结论就是,对于任意固定的 i ,如果有
然而这样还没有得出如何优化,只得到了一个判断谁更优的方法。
同样假设
如果
g(i,j)<−sum[i]
,根据上面的结论,
i点
是优于
j点
的。
如果
g(i,j)>−sum[i]
,那么有
g(j,k)>g(i,j)>−sum[i]
,根据上面的结论,虽然
j点
优于
i点
,但是有
k点
优于
j点
。
结论就是,如果存在
k<j<i
且
g(i,j)<g(j,k)
,那么
j
永远不会成为最优解,因为左边有
所以我们去除所有这样的
本来对于一个
i
,为了求
根据斜率来看,也就是任意三个点
k<j<i
,满足
kj
的斜率小于
ji
的斜率,整个曲线斜率递增,导数是为正的,形象一点可以想象
f(x)=x2
的曲线。
这种优化叫做斜率优化,它和几何斜率密切相关,膜一发CDQ女神。
现在对于一个
i
,已经知道了
因为
sum[i]
不是单调的,如果维护队首,可能会把后面需要的点出队。
根据我们维护的斜率的单调性,有一种二分的方法。
假设
l
的解集为
于是二分的时候,计算对于一个
mid
,是否满足
g(amid,amid−1)<−sum[i]
。
满足,说明
amid优于
amid−1
,那么答案应该在mid的右边。
不满足,说明
amid−1优于amid
,那么答案应该在mid的左边。
二分的正确性在于我们已经维护好了
g(ai,aj)
单调递增。
容易发现我们是在解集里求一个极值点
pos
,满足
g(apos,apos−1)<−sum[i]
且
g(apos+1,apos)>−sum[i]
。
显然求极值同样可以采用三分法。
到这里,此题已经算是解决了,可喜可贺,收获颇丰。
再加一个斜率优化DP的题目:here
附代码:
#include<stdio.h>
#include<algorithm>
using std::max;
typedef long long ll;
const int N = 2e5+5;
ll val[N], sum[N] = {0}, p[N] = {0};
int q[N], top, tail;
inline ll y(int x){ return p[x] - x*sum[x]; }
double g(int j, int k){
double dy = y(j) - y(k);
double dx = j - k;
return dy/dx;
}
inline ll getans(int i, int j){
return p[i] - p[j] - j*(sum[i] - sum[j]);
}
int solve(ll x){
int l = top, r = tail-1, mid, res = l;
while(l <= r){ //根据斜率二分求最优点
mid = (l+r) >> 1;
if(g(q[mid], q[mid-1]) < -x) l = mid+1, res = mid;
else r = mid-1;
}
return q[res];
}
int main(){
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i){
scanf("%lld", val+i);
sum[i] = sum[i-1] + val[i];
p[i] = p[i-1] + i*val[i];
}
top = tail = 0;
q[tail++] = 0;
ll ans = 0;
for(int i = 1; i <= n; ++i){
int j = solve(sum[i]); //对于固定的i,二分求最优点
ans = max(ans, getans(i,j)); //更新答案
while(top < tail-1 && g(i, q[tail-1]) < g(q[tail-1], q[tail-2])) tail--; //满足了g(i,j)<g(j,k)
q[tail++] = i;
}
printf("%lld\n", ans);
}