[斜率优化] codefores 660F. Bear and Bowling 4

F. Bear and Bowling 4
题意:
给一个序列 val ,任选连续的一段 [l,r] ,其价值为 rj=lval[j](jl+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[l1](l1)(sum[r]sum[l1]),l[1,r]
注意到里面 l l1 其实是一一对应的,为了方便,令 ans[a,b]=p[b]p[a]a(sum[b]sum[a]),a[0,b1]
这样可以得到一个 O(n2) 的暴力。

下面来优化。

设有任意三点 k<j<i ,同时假设 ans[j,i]>ans[k,i] ,把上面的 ans 代进去,我得到的结果如下:

(p[k]ksum[k])(p[j]jsum[j])jk>sum[i]

过程略,可以自行验证。

为了看起来简单,令 y(x)=(p[x]xsum[x]) ,那么上面可以写成如下形式:

y(j)y(k)jk>sum[i]

最初我推出的是这个式子,然而我用这个去维护却无法ac,后来我把左边完全化为斜率形式:
g(j,k)=y(j)y(k)jk<sum[i]

显然 g(j,k) 可以看做 j k 的斜率。
前面我们假设 j>k 并且 ans[j,i]>ans[k,i] 结果得到了这个式子。
结论就是,对于任意固定的 i ,如果有 j>k g(j,k)<sum[i] ,便可以得出对于这个 i ,选择 j 要比选择 k 更好。

然而这样还没有得出如何优化,只得到了一个判断谁更优的方法。

同样假设 k<j<i ,如果满足 g(i,j)<g(j,k) ,会发生什么情况呢?
如果 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 永远不会成为最优解,因为左边有 k ,右边有 i

所以我们去除所有这样的 j 之后,也就是不存在 g(i,j)<g(j,k) 了。
本来对于一个 i ,为了求 max(ans(l,i)) ,应该在 i 左边所有点里找到最优的 l ,现在去掉了不可能最优的点,这就是优化。
根据斜率来看,也就是任意三个点 k<j<i ,满足 kj 的斜率小于 ji 的斜率,整个曲线斜率递增,导数是为正的,形象一点可以想象 f(x)=x2 的曲线。

这种优化叫做斜率优化,它和几何斜率密切相关,膜一发CDQ女神。

现在对于一个 i ,已经知道了 max(ans(l,i)) 中, l 的解集,并且已经把不可能的点都从解集中去掉了,如何快速求出最优的 l 呢?
因为 sum[i] 不是单调的,如果维护队首,可能会把后面需要的点出队。
根据我们维护的斜率的单调性,有一种二分的方法。
假设 l 的解集为 a1,a2,a3,,an ,对于任意 k<j<i ,根据前面的结论,如果满足 g(aj,ak)<sum[i] ,那么 aj 优于 ak
于是二分的时候,计算对于一个 mid ,是否满足 g(amidamid1)<sum[i]
满足,说明 amid amid1 ,那么答案应该在mid的右边。
不满足,说明 amid1amid ,那么答案应该在mid的左边。
二分的正确性在于我们已经维护好了 g(ai,aj) 单调递增。
容易发现我们是在解集里求一个极值点 pos ,满足 g(apos,apos1)<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);
}

你可能感兴趣的:(优化,codeforces,斜率,轮廓线)