斜率优化学习笔记

斜率优化真是大坑……太考验思维了……然而似乎只要懂了之后所有题都能做了……就看你能不能把状态转移方程给抠出来……

借鉴的文章比较多……就不一一列举了……主要是莫名其妙看了两种方法的说明然后完全懵逼不知道谁对谁错,后来才发现两种是从不同的角度去说明的

 

洛谷P3195 [HNOI2008]玩具装箱TOY为例

首先我们要知道,对于形如$dp[i]=a[j]+b[j]$的式子,可以用单调队列优化到$O(n)$

但如果式子变成了形如$dp[i]=a[i]*b[j]+c[i]+d[j]$的时候,因为$a[i]*b[j]$这一项既与$i$有关又与$j$有关,所以直接单调队列行不通了

我们先来考虑这一道题目,设前缀和为$sum[i]$转移方程应该是$$dp[i]=min(dp[j]+(sum[i]+i-sum[j]-j-L-1)^2) (j<i)$$

那么我们要考虑一下进行优化,我们假设选取$j$会比$k$更优,且$k<j$

那么$$dp[j]+(sum[i]+i-sum[j]-j-L-1)^2<dp[k]+(sum[i]+i-sum[k]-k-L-1)^2$$

然后为了方便一点,令$a[i]=sum[i]+i,b[i]=sum[i]+i+L+1$

那么上式可以化为$$dp[j]+(a[i]-b[j])^2<dp[k]+(a[i]-b[k])^2$$

然后展开一下$$dp[j]+a[i]^2-2*a[i]*b[j]+b[j]^2<dp[k]+a[i]^2-2*a[i]*b[k]+b[k]^2$$

移项$$dp[j]+b[j]^2-dp[k]-b[k]^2<2*a[i]*b[j]-2*a[i]*b[k]$$

$$\frac {dp[j]+b[j]^2-dp[k]-b[k]^2}{b[j]-b[k]}<2*a[i]$$

然后令$Y[i]=dp[i]+b[i]^2,x[i]=b[i]$

那么原式可化为$$\frac {Y[j]-Y[k]}{X[j]-X[k]}<2*a[i]$$

所以如果$j>k$且$\frac {Y[j]-Y[k]}{X[j]-X[k]}<2*a[i]$ 那么$j$比$k$更优,否则$k$比$j$优

所以证了半天这有啥用么?没有

我们考虑一下,若在平面直角坐标系上存在点$k:(X[k],Y[k])$与$j:(X[j],Y[j])$,那么$\frac {Y[j]-Y[k]}{X[j]-X[k]}$就是两点的斜率

然后一下我们用$slope(i,j)$表示$i,j$两点的斜率,即$\frac {Y[i]-Y[j]}{X[i]-X[j]}$

因为$X[j],X[k],Y[i],Y[k]$都是定值,所以上面两个点都是定点

然后我们假设有三个值$i,j,k$(这里的$i$与上面的无关),其中$k<j<i$

我们假设$slope(j,k)>slope(i,j)$,也就是说是下面这个样子

 

那么$slope(j,k),slope(i,j),2*a[i]$会有三种大小关系

当$slope(j,k)>slope(i,j)>2*a[i]$时,$j$比$i$优,$k$比$j$优,$j$不是最优

当$slope(j,k)>2*a[i]>slope(i,j)$时,$k$比$j$优,$i$比$j$优,$j$不是最优

当$2*a[i]>slope(j,k)>slope(i,j)$时,$j$比$k$优,$i$比$j$优,$j$不是最优

也就是说,如果存在$slope(j,k)>slope(i,j)$,那么从$j$转移无论如何都不可能是最优的方案,已经可以把它给排除了

所以,我们必须保证维护的是一个下凸包,即$slope(j,k)>slope(i,j)$,如下图

不难看出下凸包的斜率是永远单调递增的,那么可以用一个单调队列来维护,即每一次看一看最末尾的元素$slope(q[t],q[t-1])<slope(q[t],i)$是否成立,若不成立则把$q[t]$弹出(即$--t$)

或者写成$slope(q[t],q[t-1])<slope(q[t-1],i)$也是可以的(仔细观察上图,不难发现这两个判断是等价的)

那么,在这个单调队列里哪一个答案才是最优的呢?

因为只有$slope(j,k)<2*a[i]$时$j$才会比$k$更优,而因为$a[i]=sum[i]+i$(忘了的可以回去看看,上面的假设),所以$s[i]$肯定是递增的

那么如果$slope(j,k)>2*a[i]$,那么$j$就不可能比$k$更优了,否则$j$一定比$k$优,所以我们可以把$k$从单调队列里删掉了,即每一次看看最开头的元素$slope(q[h],q[h+1])<2*a[i]$是否成立,若成立,则把$q[h]$弹出(即$++h$)

那么每一次单调队列的开头都满足$slope(q[h],q[h+1])>2*a[i]$,即$q[h]$后面的答案永远不可能比它更优。所以我们可以直接用$q[h]$来进行转移就行了,时间复杂度为$O(n)$

顺便注意一个细节,因为我们要求的$j$必须小于$i$所以我们应该先处理完单调队列开头并更新完答案,再去处理末尾

然后上本题代码

 1 //minamoto
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #define db double
 6 #define ll long long
 7 using namespace std;
 8 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
 9 char buf[1<<21],*p1=buf,*p2=buf;
10 inline int read(){
11     #define num ch-'0'
12     char ch;bool flag=0;int res;
13     while(!isdigit(ch=getc()))
14     (ch=='-')&&(flag=true);
15     for(res=num;isdigit(ch=getc());res=res*10+num);
16     (flag)&&(res=-res);
17     #undef num
18     return res;
19 }
20 const int N=50005;
21 int n,L;
22 db sum[N],dp[N];int h,t,q[N];
23 inline db a(int i){return sum[i]+i;}
24 inline db b(int i){return sum[i]+i+L+1;}
25 inline db X(int i){return b(i);}
26 inline db Y(int i){return dp[i]+b(i)*b(i);}
27 inline db slope(int i,int j){return (Y(i)-Y(j))/(X(i)-X(j));}
28 //这些都如上面定义,忘了的再去看看 
29 int main(){
30     n=read(),L=read();
31     for(int i=1;i<=n;++i) sum[i]=read()+sum[i-1];
32     h=t=1;
33     //一开始要先放一个0,而不能把它设为空 
34     for(int i=1;i<=n;++i){
35         while(h<t&&slope(q[h],q[h+1])<2*a(i)) ++h;
36         double p=a(i)-b(q[h]);
37         dp[i]=dp[q[h]]+p*p;
38         //更新答案 
39         while(h<t&&slope(q[t-1],q[t])>slope(q[t-1],i)) --t;
40         q[++t]=i;
41     }
42     printf("%lld\n",(ll)dp[n]);
43     return 0;
44 }

 

posted @ 2018-08-27 21:22  bztMinamoto  阅读(405)  评论(0编辑  收藏  举报
Live2D