题解 P3195 【[HNOI2008]玩具装箱TOY】

题目链接

刚学斜率优化,写个总结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;
}
原文地址:https://www.cnblogs.com/colazcy/p/11738846.html