P3195-[HNOI2008]玩具装箱【斜率优化dp】

正题

题目链接:https://www.luogu.com.cn/problem/P3195


题目大意

n n 个物品,分成若干段,每一段的长度为 j i + i = l r C k j-i+\sum_{i=l}^rC_k ,打包价格为 ( L ) 2 (长度-L)^2

求最小价格和。


解题思路

s i = j = 1 i C j s_i=\sum_{j=1}^iC_j
f i f_i 表示 i i 前面的都打包完了,有
f i = f j + ( s i s j + i j 1 L ) 2 f_i=f_j+(s_i-s_j+i-j-1-L)^2
f i = f j + ( s i + i s j j 1 L ) 2 f_i=f_j+(s_i+i-s_j-j-1-L)^2
然后 d p dp 可以做到 O ( n 2 ) O(n^2) ,因为有平方,考虑斜率优化
定义 a i = s i + i , b i = s j + j + 1 + L a_i=s_i+i,b_i=s_j+j+1+L

f i = f j + ( a i b j ) 2 f_i=f_{j}+(a_i-b_j)^2
f i = f j + a i 2 2 a i b j + b j 2 f_i=f_j+a_i^2-2a_ib_j+b_j^2
2 a i b j + f i a i 2 = f j + b j 2 2a_ib_j+f_i-a_i^2=f_j+b_j^2

对于每一个 j j 表示一个点 ( b j , f j + b j 2 ) (b_j,f_j+b_j^2) (后文中称之为决策点)

考虑如何使 f i f_i 最小,因为 a i 2 a_{i}^2 是定值即让 f i a i 2 f_{i}-a_{i}^2 最小,那么问题就变为了一条 y = 2 a i x + k y=2a_ix+k 的直线,要求经过某个决策点使得 k k 最小。

那么显然,可能的点一定是一个下凸壳(相邻的点斜率单调上升),而因为 2 a i 2a_i 这个斜率也是单调上升的,我们可以知道答案就是第一个决策点满足与下一个决策点的斜率 2 a i \geq 2a_i

那么我们维护一个单调队列即可。

时间复杂度 O ( n ) O(n)


c o d e code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define pow2(x) ((x)*(x))
using namespace std;
const int N=5e4+10;
struct node{
	double x,y;
	int num;
}q[N];
int n,head,tail,L;
double s[N],a[N],b[N],f[N];
double slope(node x,node y)
{return ((y.y-x.y)/(y.x-x.x));}
int main()
{
	scanf("%d%d",&n,&L);
	for(int i=1;i<=n;i++){
		scanf("%lf",&s[i]);
		s[i]+=s[i-1];
		a[i]=s[i]+i;b[i]=s[i]+i+L+1;
	}
	b[0]=L+1;
	head=tail=1;q[1]=(node){b[0],b[0]*b[0],0};
	for(int i=1;i<=n;i++){
		while(head<tail&&slope(q[head],q[head+1])<2*a[i])head++;
		int p=q[head].num;f[i]=f[p]+pow2(a[i]-b[p]);
		node w=(node){b[i],f[i]+b[i]*b[i],i};
		while(head<tail&&slope(w,q[tail-1])<slope(q[tail-1],q[tail]))
			tail--;
		q[++tail]=w;
	}
	printf("%.0lf",f[n]);
}

猜你喜欢

转载自blog.csdn.net/Mr_wuyongcong/article/details/104879891