• 【知识点】斜率优化


    斜率优化问题:

    一些形如$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 imes sum(i) imes[sum(k)-sum(j)]<dp(k)+sum(k)^{2}-[dp(j)+sum(j)^2]$

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

    $2 imes 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 imes sum(i)$的点,它就是当前转移的最优值。

    由于$2 imes 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
    ",dp[N]);
        }
        return 0;
    }
  • 相关阅读:
    03-TypeScript中的强类型
    02-TypeScript中新的字符串
    01-TypeScript概述
    2.WF 4.5 流程引擎设计思路
    1.WF 4.5在项目中直接使用的问题
    领域驱动设计-划分界限上下文
    领域驱动设计-软件挑战与应对
    领域驱动设计-基本概念
    领域驱动设计-序言
    从程序员的防猝死,思考码农身后的家庭保障
  • 原文地址:https://www.cnblogs.com/YSFAC/p/10467264.html
Copyright © 2020-2023  润新知