斜率优化与DP分析本身没关系,作用是根据转移方程,更快地找到转移最优解的状态
斜率优化模板:
任务安排1
题意:
本题暂未用到斜率优化,主要重点在于任务安排问题的DP分析方法
思路:
表示前i个任务处理完 且i是该批次最后一个任务 的最小花费
枚举上一批次的最后一个任务j
,计算三部分:
①之前的花费为
②[j+1,i]组成的批次的花费
③当前批任务的初始缓冲时间对该批次任务以及以后批次任务的时间影响
ll f[N];
//f[i] 前i个任务处理完的最小花费
ll dp(){
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
f[i]=min(f[i],f[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[j]));
}
}
return f[n];
}
任务安排2
思路:在朴素的做法中,对于每个位置i,都需要对前面的每个位置j进行判断,来取最小值。在转移方程中,可以将与j相关的项分离出,表示为
将
作为横坐标x,
作为纵坐标y,斜率为
截距为
我们的目的是让
取得最小值,在截距中,其他项都为固定值,所以只要让截距取得最小值即可
由于斜率也是固定值,且一定大于0,所以通过
点可以确定一条直线,进而确定截距
我们将所有的
画在图上,可以发现从下往上平移直线时,第一个出现在直线上的点,会使截距最小。而这些可能取得最优解的点集,构成了凸包的下边界
我们用单调队列来维护点集:
①由于斜率
的单调性,越往后直线的斜率越大,所以对于点集,当单调队列中相邻两点的斜率小于直线斜率时,就可以删除(在单调队列中)靠前的一点。
②在加入新的点后,我们要维护凸包下界,删除点集中非凸包下界的点,即在加入新的点后, 保持单调队列中相邻两点的斜率是单调递增的
ll q[N];
ll f[N];
//f[i] 前i个任务处理完 且i是该批次最后一个任务 的最小花费
ll dp(){
memset(f,0x3f,sizeof f);
f[0]=0;
int hh=0,tt=-1;
q[++tt]=0;
for(int i=1;i<=n;i++){
//删除小于直线斜率的
while(hh<tt&&(f[q[hh+1]]-f[q[hh]])<=(sumT[i]+S)*(sumC[q[hh+1]]-sumC[q[hh]])) hh++;
f[i]=f[q[hh]]+sumT[i]*(sumC[i]-sumC[q[hh]])+S*(sumC[n]-sumC[q[hh]]);
//删除非凸包下界的点
while(hh<tt&&(f[q[tt]]-f[q[tt-1]])*(sumC[i]-sumC[q[tt]])>=(f[i]-f[q[tt]])*(sumC[q[tt]]-sumC[q[tt-1]])) tt--;
q[++tt]=i;
}
return f[n];
}
任务安排3
和上一题的区别在于,斜率
的正负不确定,所以不能通过斜率来删除点,在对离直线最近的点进行查找时,使用二分进行查找斜率和直线最相近的点
ll q[N];
ll f[N];
//f[i] 前i个任务处理完 且i是该批次最后一个任务 的最小花费
ll dp(){
memset(f,0x3f,sizeof f);
f[0]=0;
int hh=0,tt=-1;
q[++tt]=0;
for(int i=1;i<=n;i++){
int l=0,r=tt;
while(l<r){
int mid=l+r>>1;
if(f[q[mid+1]]-f[q[mid]]>(sumT[i]+S)*(sumC[q[mid+1]]-sumC[q[mid]])){
r=mid;
}else{
l=mid+1;
}
}
f[i]=f[q[l]]+sumT[i]*(sumC[i]-sumC[q[l]])+S*(sumC[n]-sumC[q[l]]);
//删除非凸包下界的点
while(hh<tt&&(double)(f[q[tt]]-f[q[tt-1]])*(sumC[i]-sumC[q[tt]])>=(double)(f[i]-f[q[tt]])*(sumC[q[tt]]-sumC[q[tt-1]])) tt--;
q[++tt]=i;
}
return f[n];
}
运输小猫
题意:
思路:
ll f[M][N];
//f[i][j] 前j个 分成i组
ll y(int k,int i){
return f[i-1][k]+sumA[k];
}
ll dp(){
sort(A+1,A+m+1);
for(int i=1;i<=m;i++){
sumA[i]=sumA[i-1]+A[i];
}
memset(f,0x3f,sizeof f);
for(int i=0;i<=p;i++){
f[i][0]=0;
}
for(int i=1;i<=p;i++){
int hh=0,tt=-1;
q[++tt]=0;
for(int j=1;j<=m;j++){
while(hh<tt&&y(q[hh+1],i)-y(q[hh],i)<=A[j]*(q[hh+1]-q[hh])) hh++;
f[i][j]=j*A[j]-sumA[j]+y(q[hh],i)-A[j]*q[hh];
while(hh<tt&&(y(q[tt],i)-y(q[tt-1],i))*(j-q[tt])>=(y(j,i)-y(q[tt],i))*(q[tt]-q[tt-1])) tt--;
q[++tt]=j;
}
}
return f[p][m];
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=2;i<=n;i++){
scanf("%d",&d[i]);
sumD[i]=sumD[i-1]+d[i];
}
for(int i=1;i<=m;i++){
scanf("%d%d",&h[i],&t[i]);
A[i]=t[i]-sumD[h[i]];
}
printf("%lld",dp());
}