我们经常写DP时,我们的代码会超时
而再化简状态转移方程或则另想方法通常不容易
于是我们大佬们变想出了各种优化
这次的斜率优化便是其中一种
什么是斜率优化
表示一条直线(或曲线的切线)关于(横)坐标轴倾斜程度的量。它通常用直线(或曲线的切线)与(横)坐标轴夹角的正切,或两点的纵坐标之差与横坐标之差的比来表示。
又称“角系数”,是一条直线对于横坐标轴正向夹角的正切,反映直线对水平面的倾斜度。.一条直线与某平面直角坐标系横坐标轴正半轴方向所成的角的正切值即该直线相对于该坐标系的斜率.如果直线与x轴互相垂直,直角的正切值无穷大,故此直线不存在斜率。当直线L的斜率存在时,对于一次函数y=kx+b,(斜截式)k即该函数图像的斜率。——萌娘百度百科
总之我们只需要知道
斜率\(k=\frac{x_i-x_j}{y_i-y_j}\)
判断
对于DP中最后一层循环
取j还是取k更优(不妨设j<k)
如果 当
\(f(j) + s*g(j)<f(k)+s*g(k)\)时j更优
否则\(k\)更优
可以变形为
\(\frac{f(j)-f(k)}{g(k)-g(j)}<s\)(如果g(k)>g(j))这里我们假设如此 实际题目中需证明
反一下\(\frac{f(k)-f(j)}{g(k)-g(j)}>s\)
得到类似的式子就可以采用斜率优化啦
前置知识
单调队列,DP
具体方式
刚刚我们得到了
\(\frac{f(k)-f(j)}{g(k)-g(j)}>s\)
\(\frac{f(j)-f(k)}{g(j)-g(k)}>s\)
如图是一个上凸包
设\(k1=ij\)的斜率
设\(k2=jk\)的斜率
\(k2\)定小于\(k1\)
- 当\(k2<k1<s\)时
j优于i
k优于j
- 当\(k2<s<k1\)时
k优于j
i优于j
- 当\(s<k2<k1\)时
i优于j
j优于k
综上j是无论如何都不优的
所以在把新的东西放入单调队列中时
我们要维护一个下凸包
同时这个斜率不断增大的性质也满足单调队列的前提条件
如下图
在单调队列的拿出的操作中
\(q[]\)单调队列
当\(q[left]\)到\(q[left+1]\)的斜率>s时才行
如果图中的ij的斜率>s那么jk的斜率>ij的斜率>s
所以必有i优于j优于k优于队列后的所有点(因为队列的斜率是单调递增的)
那么也就证明了i是最优点
时间复杂度
o(DP时间+2*n(单调队列,每个点只进和出一次))
例题
讲了但不看看代码也是云里雾里的
题目大意
给定n,m
接下来给出一个长度为n的序列X
每次可以输出连续一段数
代价为\((\displaystyle\sum^{r}_{i=l}Xi)^2+m\)
求最小输出代价
容易看出 普通DP会爆
得用斜率优化来优化
Code
#include <cstdio> #include <cstring> #include <algorithm> #define M(x) (x) * (x) using namespace std; const int MAXN = 500001; int sum[MAXN],dp[MAXN],m; template<typename T> inline T Min(T a,T b) {if(a < b) return a;return b;} inline int Get(int i,int j) { return dp[i] + M(sum[i]) - dp[j] - M(sum[j]); } inline int Get2(int i,int j) { return sum[i] - sum[j]; } inline int Getdp(int i,int j) { return dp[j] + M(sum[i] - sum[j]) + m; } int main() { int n; while(~scanf("%d%d",&n,&m)) { int i,j; sum[0] = 0; for(i = 1;i <= n;i++) { scanf("%d",&sum[i]); sum[i] += sum[i - 1]; } int head = 0,tail = 1,q[MAXN] = {}; dp[0] = 0; q[0] = 0; for(i = 1;i <= n;i++) { while(head + 1 < tail&&Get(q[head + 1],q[head]) <= 2 * sum[i] * Get2(q[head + 1],q[head])) head++; dp[i] = Getdp(i,q[head]); while(head + 1 < tail&&Get(i,q[tail - 1]) * Get2(q[tail - 1],q[tail - 2]) <= Get(q[tail - 1],q[tail - 2]) * Get2(i,q[tail - 1])) tail--; q[tail] = i; tail++; } printf("%d\n",dp[n]); } }
紫题
然后 到这里
我们就可以去做这道题了
前置知识:斜率优化,方差