斜率优化总结

考虑如下 d p dp dp

d p ( i ) = m a x / m i n ( A ( i ) + B ( j ) + C ( i ) D ( j ) ) dp(i)=max/min(A(i)+B(j)+C(i)D(j)) dp(i)=max/min(A(i)+B(j)+C(i)D(j)) ( j &lt; i ) (j &lt; i) (j<i)

其中,A(i),C(i)只与i有关,B(j),D(j)只与j有关。

括号里有与i,j同时有关的项,导致单调队列优化失效,但是如果这样的项只有一个可以采用斜率优化。方法如下:

将此dp方程转化为 d p ( i ) − A ( i ) = m a x / m i n ( B ( j ) − C ( i ) ( − D ( j ) ) ) dp(i)-A(i)=max/min(B(j)-C(i)(-D(j)) ) dp(i)A(i)=max/min(B(j)C(i)(D(j)))的形式。

设b=dp(i)-A(i),y=B(j),k=C(i),x=-D(j)

b = y − k x b=y-kx b=ykx,这时,只要使b最小/最大,就能使结果最小/最大。

移项,得 y = k x + b y=kx+b y=kx+b,转化为直线的形式。每计算完一个j就可以计算出对应的x,y.

其实就是把要最优的值设为b,只和j相关的项设为y,同时相关的设为kx。

还有类似 b = a y − k x b=ay-kx b=aykx的形式(两项相关),只要转为 b / a = y − ( k / a ) ∗ x b/a=y-(k/a)*x b/a=y(k/a)x即可。

以最大为例(最小类似),现在就是要在所有的(x,y)中找出一对使得b最大。

把(x,y)表示为坐标系中点的形式,就是找一个点使得斜率为k的直线经过这个点时与y轴的交点的y坐标尽量大。

可以发现把斜率为k的直线放在无限高处,再向下平移,接触到的第一个点就是结果。

如图:
这里写图描述

因为要求最大值,所以只有上凸包上的点有可能成为最大值,其他点可以删除。

这里写图片描述

这里写图片描述

这时,红色箭头即为b,绿色箭头即为最优的(x,y)。dp(i)就是b+A(i)。

如果x单调,k也单调,就可以用队列或栈来维护凸包。(时间复杂度 O ( N ) O(N) O(N)

如果k不单调,可以在凸包上二分,找到第一个斜率小于k的位置,并计算解。(时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)

如果x不单调,可以用splay维护凸包,或利用CDQ分治,在每层将x,k排序,使x,k递增。(时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)

例题:bzoj1911[Apio2010] 特别行动队

设 f[i] 表示将前 i 个分组的最优值,则有转移方程式:

f [ i ] = m a x ( f [ j ] + a × ( C [ i ] − C [ j ] ) 2 + b × ( C [ i ] − C [ j ] ) + c ) f[i]=max( f[j]+a×(C[i]-C[j])^2+b×(C[i]-C[j])+c ) f[i]=maxf[j]+a×(C[i]C[j])2+b×(C[i]C[j])+c

经过化简得到:

f [ i ] = m a x ( f [ j ] + a × C [ j ] 2 − b × C [ j ] − 2 × a × C [ i ] × C [ j ] + a × C [ i ] 2 + b × C [ i ] + c ) f[i]=max( f[j]+a×C[j]^2-b×C[j]-2×a×C[i]×C[j]+a×C[i]^2+b×C[i]+c ) f[i]=max(f[j]+a×C[j]2b×C[j]2×a×C[i]×C[j]+a×C[i]2+b×C[i]+c)

A ( i ) = a × C [ i ] 2 + b × C [ i ] + c A(i)=a×C[i]^2+b×C[i]+c A(i)=a×C[i]2+b×C[i]+c

B ( j ) = f [ j ] + a × C [ j ] 2 − b × C [ j ] B(j)=f[j]+a×C[j]^2-b×C[j] B(j)=f[j]+a×C[j]2b×C[j]

C ( i ) = 2 × a × C [ i ] C(i)=2×a×C[i] C(i)=2×a×C[i]

D ( j ) = − C [ j ] D(j)=-C[j] D(j)=C[j]

就可以进行斜率优化了。且x单调递增,k单调递减,可以使用队列维护凸包。

代码:

#include <stdio.h>
#define ll long long
ll dp[1000010];
int sz[1000010],S[1000010];
ll X[1000010],Y[1000010],he=0,ta=0;
int main()
{
    int n,a,b,c;
    scanf("%d%d%d%d",&n,&a,&b,&c);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&sz[i]);
        S[i]=sz[i]+S[i-1];
    }
    for(int i=0;i<=n;i++)
    {
        if(i==0)
            dp[i]=0;
        else
        {
            int k=2*a*S[i];
            while(he+1<ta&&Y[he]-k*X[he]<Y[he+1]-k*X[he+1])//从队首删除点
                he+=1;
            dp[i]=Y[he]-k*X[he]+(ll)a*S[i]*S[i]+(ll)b*S[i]+c;//计算dp值
        }
        ll x=S[i],y=dp[i]+(ll)a*S[i]*S[i]-(ll)b*S[i];//计算x,y值
        while(he+1<ta&&double(y-Y[ta-1])/(x-X[ta-1])>double(Y[ta-1]-Y[ta-2])/(X[ta-1]-X[ta-2]))//入队
            ta-=1;
        X[ta]=x,Y[ta]=y;
        ta+=1;
    }
    printf("%lld",dp[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值