搞了一晚上斜率优化,大概懂了一点,写写
原来常用的优化dp方法:做前缀和,预处理,数据结构维护
现在有转移方程长这样的一类dp:$dp[i]=min(dp[i],k[i]*x[j]+y[j]+c[i]+a)$,其中$c[i],k[i],x[j],y[j]$都是关于$i$或者$j$的变量,在$i$或者$j$确定时不变,$a$是个常量
然后发现$x[j]$带着一个$k[i]$的系数,不好优化
从另一个角度考虑,想想高中老师教给我们的线性规划
可以发现因为对于每次转移的$i$来说$c[i],k[i]$都不变,我们可以把$k[i]*x[j]+y[j]$看做是一条直线(初中的一次函数),$k[i]$是斜率,然后$x[j]$是横坐标,$y[j]$是纵坐标,别的都是关于$i$的变量或者常量,不用管。那么实际上我们求的$dp$数组的最终结果就是这条直线的截距的最值(初中的与y轴的交点)
然后我们发现发现对于每个下标我们都可以依照上面的$x[j],y[j]$把它表示成平面上的一个点$(x,y)$,然后这些点会形成一个点集,根据线性规划的知识,可以发现根据斜率的正/负我们的最优决策点都在下/上凸包上,于是可以优化了
当我们每次转移用到的斜率单调时,直接用单调队列维护凸包,先把斜率大/小的都踢掉,转移之后再把现在不在凸包上的点也都踢掉,最后把当前点加进去
当我们每次转移用到的斜率不单调时,就不能根据斜率直接踢了,但仍然用单调队列维护凸包,只是把找最优决策点用二分代替
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=50005; 6 long long len[N],dp[N],que[N],n,x,f,b; 7 inline long long c1(int p){return len[p]+p;} 8 inline long long c2(int p){return c1(p)+x+1;} 9 inline long long K(int p){return 2*c1(p);} 10 inline long long X(int p){return c2(p);} 11 inline long long Y(int p){return c2(p)*c2(p)+dp[p];} 12 inline long long S(int a,int b){return (double)(Y(b)-Y(a))/(double)(X(b)-X(a));} 13 int main() 14 { 15 scanf("%lld%lld",&n,&x); 16 for(int i=1;i<=n;i++) 17 scanf("%lld",&len[i]),len[i]+=len[i-1]; 18 que[f=b=0]=0; 19 for(int i=1;i<=n;i++) 20 { 21 while(b-f>=1&&S(que[f],que[f+1])<K(i)) f++; 22 dp[i]=dp[que[f]]+(c1(i)-c2(que[f]))*(c1(i)-c2(que[f])); 23 while(b-f>=1&&S(que[b-1],i)<S(que[b],que[b-1])) b--; que[++b]=i; 24 } 25 printf("%lld",dp[n]); 26 return 0; 27 }