算法导论————斜率优化

【例题传送门:BZOJ1010


BZOJ1010: [HNOI2008]玩具装箱toy

【题意】
给出n条连续线段,每条线段都有长度为x[i],咱们能够把连续若干条线段连在一块儿,变成一个组合,两条线段若是相连,就要在两条线段中间添加一个长度为1的格子(若是没有相连就不用添加),假如咱们如今选择把第i条到第j条线段之间的全部线段变成一组合的话,这个组合的总长度就为:x[i]+x[i+1]+x[i+2]+x[i+3]+...+x[j]+j-i,如今给出一个常数L,假设当前选择的组合的长度为s,那么这个组合就为咱们产生了(s-L)^2的费用,求出把n条线段分红若干组合所须要的最小费用,单独的线段能够成为一个组合
【输入文件】
第一行两个整数,分别为N和L。
下来N个数字xi,按编号从小到大输入每一个物品的容量。
1<=N<=50000,1<=L,xi<=10^7
【输出文件】
一个整数,总花费的最小值。
【样例输入】
5 4
3 4 2 1 4
【样例输出】
1
php


算法分析:

  斜率优化其实就是一个用来优化DP的算法,但必须当DP方程具备单调性的时候才能使用算法

  下来咱们用例题来讲明斜率优化函数

  f[i]表示1~i的最小花费。
  f[i]=min(f[j]+(sum[i]-sum[j]+i-(j+1)-L)^2) (j<i)
  f[i]=min(f[j]+(sum[i]+i-sum[j]-j-1-L)^2) (j<i)
  令s[i]=sum[i]+i,L=1+L
  则f[i]=min(f[j]+(s[i]-s[j]-L)^2)
优化

 

  首先咱们先来证实决策单调性
  假设j1<j2<i,在状态i处的j2决策不比j1决策差(内心想着淘汰j1),
  即要知足:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2
  则对于i后的全部状态t,是否j2也不比就j1差?(术语:证实决策单调性)
  即f[j2]+(s[t]-s[j2]-L)^2 < f[j1]+(s[t]-s[j1]-L)^2
  容易理解s[t]=s[i]+v
  因此获得(1)不等式:f[j2]+(s[i]-s[j2]-L+v)^2<f[j1]+(s[i]-s[j1]-L+v)^2
  由于已知(2)不等式:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2
  因此化简(1)不等式:把s[i]-s[j2]-L当作一个总体,v当作一个总体,获得:
  f[j2]+(s[i]-s[j2]-L)^2+2*v*(s[i]-s[j2]-L)+v^2 <f[j1]+(s[i]-s[j1]-L)^2+2*v*(s[i]-s[j1]-L)+v^2spa

  比较(2)不等式:
  左边多了一部分:2*v*(s[i]-s[j2]-L)+v^2
  右边多了一部分:2*v*(s[i]-s[j1]-L)+v^2
  因此咱们只须要证:
  2*v*(s[i]-s[j2]-L)+v^2<=2*v*(s[i]-s[j1]-L)+v^2
  即:(s[i]-s[j2]-L)<=(s[i]-s[j1]-L)
  即: -s[j2] <= -s[j1]
  即:s[j1]<s[j2]这是确定的,因此得证。
  总结:对于当前i:j2比j1好,那么对于t(i<t)来讲同样:j2同样比j1好,
  因此当前i选择j2,淘汰j1,之后的t也不会在j2存在的时候选择j1
  因此i的时候就能够永久淘汰j1code

  而后来求斜率方程
  由于f[j2]+(s[i]-s[j2]-L)^2<=f[j1]+(s[i]-s[j1]-L)^2
  展开:
  f[j2]+(s[i]-L)^2-2*(s[i]-L)*s[j2]+s[j2]^2<=f[j1]+(s[i]-L)^2-2*(s[i]-L)*s[j1]+s[j1]^2
  即f[j2]-2*(s[i]-L)*s[j2]+s[j2]^2<=f[j1]-2*(s[i]-L)*s[j1]+s[j1]^2
  即f[j2]+s[j2]^2-2*(s[i]-L)*s[j2]<=f[j1]+s[j1]^2-2*(s[i]-L)*s[j1]
  即[(f[j2]+s[j2]^2)-(f[j1]+s[j1]^2)]<=2*(s[i]-L)*s[j2]-2*(s[i]-L)*s[j1]
  即[(f[j2]+s[j2]^2)-(f[j1]+s[j1]^2)]/(s[j2]-s[j1])<=2*(s[i]-L)
  对于j来讲:
  制造的点坐标
  Y=f[j]+s[j]^2
  X=s[j]
  咱们用队列list在存有意义的决策点,list中相邻两点的斜率递增(队列中的点造成一个下凸壳),并且都大于2*(s[i]-L),那么队列头对于i来讲就是最优决策点
  加入决策i时,令队尾为list[tail],前一个为list[tail-1]blog

  斜率函数slop(点1,点2)
  知足:slop(list[tail-1],list[tail])>slop(list[tail],i)时,
  那么队尾list[tail]在三者(list[tail-1],list[tail],i)对于将来的tail绝对不会是最优的策略,因此将其弹出tail--
  最后遇到了:slop(list[tail-1],list[tail])<slop(list[tail],i),保证了队列的相邻两点的斜率递增因此加入i:list[++tail]=i;队列

  而后f[n]就是答案了get


参考代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
typedef long long LL;
LL a[51000],s[51000],f[51000];
/*
f[i]=min(f[j]+(sum[i]-sum[j]+i-j-1-L)^2)
f[i]=min(f[j]+(s[i]-s[j]-L)^2)
f[j2]+(s[i]-s[j2]-L)^2<=f[j1]+(s[i]-s[j1]-L)^2
f[j2]+(s[i]-L)^2-2*s[j2]*(s[i]-L)+s[j2]^2<=f[j1]+(s[i]-L)^2-2*s[j1]*(s[i]-L)+s[j1]^2
f[j2]+s[j2]^2-2*s[j2]*(s[i]-L)<=f[j1]+s[j1]^2-2*s[j1]*(s[i]-L)
f[j2]-f[j1]+s[j2]^2-s[j1]^2<=2*s[j2]*(s[i]-L)-2*s[j1]*(s[i]-L)
f[j2]-f[j1]+s[j2]^2-s[j1]^2<=2*s[j2]*(s[i]-L)-2*s[j1]*(s[i]-L)
(f[j2]-f[j1]+s[j2]^2-s[j1]^2)/(s[j2]-s[j1])<=2*(s[i]-L)
*/
double slop(int j1,int j2)
{
    return (f[j2]-f[j1]+s[j2]*s[j2]-s[j1]*s[j1])/(s[j2]-s[j1]);
}
int list[51000];int head,tail;
int main()
{
    LL L;int n;
    scanf("%d%lld",&n,&L);L++;
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    s[1]=a[1]+1;
    for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i]+1;
    head=1;tail=1;list[1]=0;
    for(int i=1;i<=n;i++)
    {
        while(head<tail&&slop(list[head],list[head+1])<=2.0*(s[i]-L)) head++;
        int j=list[head];
        f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L);
        while(head<tail&&slop(list[tail],i)<slop(list[tail-1],list[tail])) tail--;
        list[++tail]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}
相关文章
相关标签/搜索