斜率优化

数学证明

数学方面的理解及证明在zzk的博客里

几何证明

个人比较偏向于几何证明,直观清晰
非常推的博客大米饼

EX 1. 玩具装箱

步骤一:

  • 列出DP方程式:设f[i]表示分组完前i件物品的最小花费,为方便计算,
  • 设sum[i]表示是前i件物品的长度和。
  • f[i]=Min(f[j]+(sum[i]-sum[j]+i-j-L-1)2) [0<=j<i]

步骤二:

  • 对于范围内的j,进行式子变形,获得直线解析式:(先让L++)
  • f[i]=f[j]+[(sum[i]+i)-(sum[j]+j)-L]2------->令s[k]=sum[k]+k
  • f[i]=f[j]+(s[i]-s[j]-L)2----------------->整体法展开
  • f[i]=f[j]+s[i]2+(s[j]+L)2-2s[i](s[j]+L)---->移项
  • f[i]+2s[i](s[j]+L)=f[j]+s[i]2+(s[j]+L)2---->转换为一般格式
    即 b + kx = y

对于任意j可以表示为图像上一点J(s[j]+L,f[j]+s[i]2+(s[j]+L)2),目的是求出f[i],因为f[i]的值等于y=kx+b直线的截距。

步骤三:

问题转化为:对于一条斜率为k=2s[i]的直线(为什么将它作为斜率,请思考),使其至少过一点J(即上文那个任意J点,j<i),使得该直线的截距最小(其实际意义是为P教授省钱)。那么考虑哪些J点有望成为答案?
观赏下图
图
首先确定直线斜率的变化特点。由于k=s[i]
2所以我们获得两个重要信息,即k>0并且k随i单调递增。我们将这个斜率画到图上,直线变化如下:
图
直线斜率的单调递增使得我们能够优化DP。怎么优化?优化的基础是,对于本题,在求最小值的背景下,我们维护一个下凸包,然后随着i的不断循环,凸包的尾巴(横坐标较小部分)会被逐渐削减,同时前端会不断变化。这个过程能够被单调队列完美胜任。去尾巴的原因在下图:
图
由上文结论可以知道,只要我们的f[i]线过了一个J点,那么表示的是f[j]向 f[i]的状态转移。而我们的目的是使得f[i]线过了一个点并使其的截距最小(当然图中有不合理之处,本题中直线截距应当大于0)。图中可以看出,线段AB的斜率小于f[i]线的斜率,那么意味着f[i]线截距最小值时肯定不会过点A,因为可以通过平移到达更美妙的点B或点C等。所以我们就需要删掉队列中A点,因为后来的所有f[i]斜率必将大于AB,A点将会像凸包内的点一样对答案毫无价值。那么在前端加入点怎么解释呢?那也是同样的道理:
图
在完成f[i]的状态转移后,i++。那么此时以前的f[i]也就成了f[j]中的一员,我们将它标记为点NEW。这个点是否有资格进入单调队列呢(即是否有资格作为候选的状态转移来源呢)就需要再次利用上文相同的思想。如图,如果 NEW点与A点组成的直线斜率小于线段AB,BC那么我们就要将BC两点从队首弹出,原因是既然有了NEW点的存在,那么以后的直线更愿意选择NEW点,因为这样会使截距更小。
在代码实现“去除尾巴”和“清理头部”中,都是以判断斜率为标准。其实这类DP代码就是在原来基础上增加了单调队列的两排操作和一个求斜率的函数调用。

提醒:上文中的关于j的式子里出现了s[i]2,但不要错误理解为双变量,因为在对于同一个f[i]计算时,求斜率坐标相减就已经直接抵消掉了这个关于i的部分。

 1 #include<cstdio>
 2 #define ll long long
 3 #define Empty (head>=tail)
 4 #define go(i,a,b) for(int i=a;i<=b;i++)
 5 const int N=50100;ll s[N],Q[N],f[N],n,x,head,L,tail,j;
 6 inline double X(ll i){return s[i];}
 7 inline double Y(ll i){return f[i]+(s[i]+L-1)*(s[i]+L-1);}
 8 inline double Rate(ll i,ll k){return (Y(k)-Y(i))/(X(k)-X(i));}
 9 int main()
10 {
11     scanf("%lld%lld",&n,&L);s[0]=0;L++;head=1;tail=1;Q[1]=0;
12     go(i,1,n){scanf("%lld",&s[i]);s[i]+=s[i-1];}go(i,1,n)s[i]+=i;
13     go(i,1,n)
14     {
15         while(!Empty&&Rate(Q[head],Q[head+1])<2*s[i])head++;
16         j=Q[head];f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L);
17         while (!Empty&&Rate(Q[tail-1],Q[tail])>Rate(Q[tail],i))tail--;Q[++tail]=i;
18     }
19     printf("%lld\n",f[n]); return 0;
20 }

posted @ 2018-11-06 17:24  lnyzo  阅读(110)  评论(0)    收藏  举报