斜率优化最详解析

[HNOI2008]玩具装箱 - 洛谷

首先用dp[i]表示前i个装进容器时所需的最小花费,可以得出的是,本次的i可以单独放进一个,可以和i-1,i-2...1放进一个,也就是

dp[i]= dp[j] + (sum[i]-sum[j] + j-i-1-L)^2 本次长度的费用 其中sum[i]-sum[j]表示j+1--i之间的C值,也就是说j的取值范围为1<=j<i。

令A[i]等于 sum[i]-i

令B[i]等于 sum[j]-j+1+L

dp[i]= dp[j] + (sum[i]-sum[j] + j-i-1-L)^2

也就是 dp[i] =min ( dp[j]+(A[i]-B[j])^2 )

拆分之后

dp[i] = dp[j] + A[i]^2 + B[j] ^2 -2*A[i]*B[j]

之后,我们把关于j的一切用关于i的来线性表示

dp[j] +B[j]^2 = 2*A[i]*B[j] -A[i]^2 +dp[i]

      y         =     k        x     +b 

y-kx就是截距,而dp[i]就代表了,截距加上A[i]^2之后的结果。

求dp[i]的最小值转变为在    x=B[j],y=dp[j]+B[j]^2时    求截距的最小值。

如图所示,ABCD代表了单调队列队首到队尾的全部元素组成的一个“凸包”,坐标分别是  x=B[j],y=dp[j]+B[j]^2,EF是本次i代表的直线;我们要通过这条直线与每个点的相交来实现算出截距的目的,但每个点都找无疑会超时

这里有一个结论,想让i在此取到最小截距,必须在斜率第一个大于该点的位置,本题在C点

  while(head<tail&&slope(q[head],q[head+1])<2*A(i))
        head++;

上部分代码,自动去除了AB两点,本次取出之后,以后也不会用到,因为本题A[i]是递增的 

如果斜率全部都小于2A[i],那么其实就是最后只剩余最后一个点,借助图像仍能看出,最后一个点是能产生最小截距的,如本图D点就能产生最小截距 

 

 

 

 然后我们就更新出来了,小于i的最优解,更新即可。

 dp[i]=dp[q[head]]+(A(i)-B(q[head]))*(A(i)-B(q[head]));

然后,我们要维护凸包 

 在我们加入E点时,我们比较E点和倒数第二个点构成的斜率以及倒数第二点和倒数第一点构成的斜率,一旦前者小于后者,那么我们就能将D点包含进凸包,更新。因为在“外侧的点”在产生截距时,会产生绝对值更大而值更小的截距。

   while(head<tail&&slope(i,q[tail-1])<slope(q[tail-1],q[tail]))
            tail--;
#include<iostream>
#include<cstdio>
#include<cmath>
#define maxn 50005
#define ll long long
using namespace std;



ll sum[50000+10],a[50000+10];


ll dp[500000+10];


ll n,L;
ll A(int i)
{
    return sum[i]+i;

}
ll B(int i)
{
    return A(i)+L+1;

}

ll X(int i)
{
    return B(i);

}

ll Y(int i)
{
    return dp[i]+B(i)*B(i);

}

double slope(int i,int j)
{

    return double(Y(i)-Y(j))*1.0/(X(i)-X(j));

}

ll q[1000000+10];

int main()
{



	cin>>n>>L;


	for(int i=1;i<=n;i++)
    {
        cin>>a[i];

        sum[i]=sum[i-1]+a[i];

    }


    /*


    dp[i]=dp[j]+(A[i]-B[j])^2  (j<i)

    A[i]=sum[i]+i

    B[j]=sum[j]+j+1+L

    dp[i]= dp[j] +A[i]^2 -2 A[i] B[j] +B[j]^2

    2*a[i]*b[j]+ dp[i]-a[i]^2 = dp[j]+b[j]^2

      k     x                        y

    */



    int head=1;

    int tail=1;


    for(int i=1;i<=n;i++)
    {
        while(head<tail&&slope(q[head],q[head+1])<2*A(i))
        head++;

        dp[i]=dp[q[head]]+(A(i)-B(q[head]))*(A(i)-B(q[head]));


        while(head<tail&&slope(i,q[tail-1])<slope(q[tail-1],q[tail]))
            tail--;

        tail++;

        q[tail]=i;

    }

    cout<<dp[n];

	return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦三码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值