正题
题目链接:https://www.luogu.com.cn/problem/P3195
题目大意
nnn个物品,分成若干段,每一段的长度为j−i+∑i=lrCkj-i+\sum_{i=l}^rC_kj−i+∑i=lrCk,打包价格为(长度−L)2(长度-L)^2(长度−L)2
求最小价格和。
解题思路
si=∑j=1iCjs_i=\sum_{j=1}^iC_jsi=∑j=1iCj
设fif_ifi表示iii前面的都打包完了,有
fi=fj+(si−sj+i−j−1−L)2f_i=f_j+(s_i-s_j+i-j-1-L)^2fi=fj+(si−sj+i−j−1−L)2
fi=fj+(si+i−sj−j−1−L)2f_i=f_j+(s_i+i-s_j-j-1-L)^2fi=fj+(si+i−sj−j−1−L)2
然后dpdpdp可以做到O(n2)O(n^2)O(n2),因为有平方,考虑斜率优化
定义ai=si+i,bi=sj+j+1+La_i=s_i+i,b_i=s_j+j+1+Lai=si+i,bi=sj+j+1+L
有
fi=fj+(ai−bj)2f_i=f_{j}+(a_i-b_j)^2fi=fj+(ai−bj)2
fi=fj+ai2−2aibj+bj2f_i=f_j+a_i^2-2a_ib_j+b_j^2fi=fj+ai2−2aibj+bj2
2aibj+fi−ai2=fj+bj22a_ib_j+f_i-a_i^2=f_j+b_j^22aibj+fi−ai2=fj+bj2
对于每一个jjj表示一个点(bj,fj+bj2)(b_j,f_j+b_j^2)(bj,fj+bj2)(后文中称之为决策点)
考虑如何使fif_ifi最小,因为ai2a_{i}^2ai2是定值即让fi−ai2f_{i}-a_{i}^2fi−ai2最小,那么问题就变为了一条y=2aix+ky=2a_ix+ky=2aix+k的直线,要求经过某个决策点使得kkk最小。
那么显然,可能的点一定是一个下凸壳(相邻的点斜率单调上升),而因为2ai2a_i2ai这个斜率也是单调上升的,我们可以知道答案就是第一个决策点满足与下一个决策点的斜率≥2ai\geq 2a_i≥2ai。
那么我们维护一个单调队列即可。
时间复杂度O(n)O(n)O(n)
codecodecode
#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]);
}