刚学斜率优化,写个总结qaq
Solution [HNOI2008]玩具装箱TOY
题目大意:转移方程(f[i] = min{f[j]+(sum[i]-sum[j]+i-j-1-L)^2}),求(f[n])
斜率优化
分析:首先我们肯定不能(O(n^2))做(dp),我们来拆一波式子,先把(min)符号去掉,然后令(L=L+1)
(f[i] = f[j]+(sum[i]-sum[j]+i-j-1-L)^2)
(f[i] = f[j]+(sum[i]-sum[j]+i-j-L)^2)
考虑换元,用(s[i])代表(sum[i]+i)
(f[i]=f[j]+(s[i]-s[j]-L)^2)
完全平方公式
(f[i]=f[j]+s[i]^2-2 imes s[i] imes (s[j]+L)+(s[j]+L)^2)
然后我们发现有的和(i)有关,有的和(j)有关,有的和两个都有关,我们考虑将其分类:
和(i)有关的(f[i],s[i]^2)
和(j)有关的(f[j],(s[j]+L)^2)
都有关的:(-2 imes s[i] imes (s[j]+L))
我们考虑将其改写为(y=kx+b)的形式,将和(i)有关的作为(b),将和(i,j)都有关的作为(k,x),由于是乘积,我们将其中有(i)的项作为(k),和(j)有关的作为(x),然后将只和(j)有关的作为(y)
所以
(y=f[j]+(s[j]+L)^2)
(k=2 imes s[i])
(x=s[j]+L)
(b=f[i]-s[i]^2)
建立平面直角坐标系,以上文的(x,y)把决策点放进坐标系。然后我们发现问题变成了,一个坐标系内有若干个点,一个斜率不变的直线经过任意一个点,使得截距最小化
我们往坐标系内加入决策点时,(x)坐标是递增的,一个显而易见的结论:有用的决策点的集合,过相邻点直线的斜率一定单调递增
于是我们可以用单调队列来维护这个下凸壳使得斜率单调递增。
那么我们如何这些决策点集合里面取最优呢,因为(k)是单调递增的,所以每次做决策时的直线斜率单调递增,一个点能成为决策点当且仅当左边直线的斜率比(k)小,右边直线的斜率比(k)大,又因为斜率单调递增,我们也可以用单调队列来维护,不断弹队头即可
因为每个点只会进队/出队一次,所以时间复杂度是(O(n))的
#include <iostream>
using namespace std;
const int maxn = 5e4 + 100;
typedef long long ll;
int n,L,head,tail,q[maxn];
ll sum[maxn],f[maxn];
template <typename T>
inline T square(const T &x){return x * x;}
inline double s(int i){return sum[i] + i;}
inline double k(int i){return 2 * s(i);}
inline double y(int i){return f[i] + square(s(i) + L);}
inline double x(int i){return s(i) + L;}
inline double b(int i){return f[i] - square(s(i));}
inline double slope(int i,int j){return (y(j) - y(i)) / (x(j) - x(i));}
int main(){
ios::sync_with_stdio(false);
cin >> n >> L;L++;
for(int i = 1;i <= n;i++)
cin >> sum[i],sum[i] += sum[i - 1];
head = tail = 1;
for(int i = 1;i <= n;i++){
while(head < tail && slope(q[head],q[head + 1]) < k(i))head++;
f[i] = f[q[head]] + square(s(i) - s(q[head]) - L);
while(head < tail && slope(i,q[tail - 1]) < slope(q[tail - 1],q[tail]))tail--;
q[++tail] = i;
}
cout << f[n] << '
';
return 0;
}