【知识点】斜率优化

斜率优化问题:

一些形如$dp(i)=min\{dp(i),dp(j)+f(i)*A(j)\}$的转移方程无法用单调队列优化。然而时间复杂度又不能$O(n^2)$。

这种情况下对于$dp(i)$,假如从$j$转移比从$k$转移更优,$j,k$需要满足一些条件。

我们通过整理这些条件可以将每个$i$抽象成坐标系中的一个点并用单调队列维护上凸/下凸包解决问题。

 

思路:

我们以HDU3507为例:

容易得出转移方程:$dp(i)=min\{dp(i),dp(j)+[sum(i)-sum(j)]^{2}+M\}$,其中$sum(i)=\sum_{k=1}^{i}C_k$。

然后发现好像不太会优化……不如打个暴力走人

对于某个i,假设从j转移比从k转移更优,则我们有:

$dp(j)+[sum(i)-sum(j)]^{2}+M<dp(k)+[sum(i)-sum(k)]^{2}+M$

经过一些代数变换,我们得到:

$2\times sum(i)\times[sum(k)-sum(j)]<dp(k)+sum(k)^{2}-[dp(j)+sum(j)^2]$

我们设j位于k前面,那么$sum(k)-sum(j)>0$,移项得到:

$2\times sum(i)<\frac{dp(k)+sum(k)^{2}-[dp(j)+sum(j)^2]}{sum(k)-sum(j)}$

发现右边就是过点$A(sum(k),dp(k)+sum(k)^{2})$和点$B(sum(j),dp(j)+sum(j)^{2})$的直线$AB$的斜率。

那么可以将每个$dp(i)$都按上面的方式转化成一个点放入坐标系中。

画图可以发现如果存在三个点形成上凸,那么中间那个点一定不是最优。

于是我们维护一些相邻两个点连线的斜率递增的点(下凸包)作为答案序列,

每次找到第一个与它右边的点连线的斜率$>2\times sum(i)$的点,它就是当前转移的最优值。

由于$2\times sum(i)$单调递增,我们可以用单调队列维护之,即每次删除答案点左边的所有点。

每个点只会入队出队一次,所以复杂度是线性的。

 

其他形式:

若$F_i =A_i x_j +B_i y_j$,则有$y_j = -\frac{A_i }{B_i } x_j +\frac{F_i }{B_i }$。

不难发现该式的几何意义为令一条斜率为$-\frac{A_i }{B_i }$的直线依次经过每个点$(x_j , y_j)$,求最大截距。

则答案必然在上凸壳/下凸壳上,其余处理同上文。

该形式较为通用,可以避免分类讨论正负性。

 

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>

using namespace std;
#define MAXN 1000005
#define MAXM 500005
#define INF 0x7fffffff
#define ll long long

inline ll read(){
    ll x=0,f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar())
        if(c=='-')
            f=-1;
    for(;isdigit(c);c=getchar())
        x=x*10+c-'0';
    return x*f;
}

ll N,M,C[MAXN],S[MAXN],q[MAXN],dp[MAXN];

inline ll getk(ll x1,ll x2) {return (dp[x2]+S[x2]*S[x2])-(dp[x1]+S[x1]*S[x1]);}

int main(){
    while(scanf("%d%d",&N,&M)!=EOF){
        for(ll i=1;i<=N;i++)
            C[i]=read(),S[i]=S[i-1]+C[i];
        ll head=1,tail=1; q[head]=0;
        for(ll i=1;i<=N;i++){
            while(head<tail && getk(q[head],q[head+1])<=2*S[i]*(S[q[head+1]]-S[q[head]])) head++;
            dp[i]=dp[q[head]]+(S[i]-S[q[head]])*(S[i]-S[q[head]])+M;
            while(head<tail && getk(q[tail-1],q[tail])*(S[i]-S[q[tail]])>=getk(q[tail],i)*(S[q[tail]]-S[q[tail-1]])) tail--;
            q[++tail]=i;
        }
        printf("%lld\n",dp[N]);
    }
    return 0;
}

 

posted @ 2019-03-03 20:43  Fugtemypt  阅读(240)  评论(0编辑  收藏  举报