【模板】斜率优化

斜率优化问题:

一些形如$dp(i)=min\{dp(i),dp(j)+f(i)*(\cdots)\}$的转移方程无法用单调队列优化。然而时间复杂度又不能$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)$单调递增,我们可以用单调队列维护之,即每次删除答案点左边的所有点。

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

 

代码:

#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;
}

 

转载于:https://www.cnblogs.com/YSFAC/p/10467264.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值