描述
这道题目说的是,给出了n项必须按照顺序完成的任务,每项任务有它需要占用机器的时间和价值。现在我们有一台机器可以使用,它每次可以完成一批任务,完成这批任务所需的时间为一个启动机器的时间S加上所有任务需要的时间。并且它是在完成所有任务后才会把任务的成果输出,这样我们就在那一时间时得到所有这些任务的一个完成时间。我们现在要求一种完成任务的方式使得所有任务的完成时间乘上该任务的价值之和最小。
输入
第一行,一个数n表示任务的总数
第二行,一个数s表示开机的时间
接下来n行,每行两个数a,b,a表示该任务完成所要的时间,b表示该任务的价值。
输出
输出文件包括一行,这一行只包含一个整数,就是最少价值和。
样例输入
5
1
1 3
3 2
4 3
2 3
1 4
样例输出
153
提示
【样例说明】
分组情况:
{1,2},{3},{4,5}
T=(s+1+3)(3+2)+(s+s+1+3+4)3+(s+s+s+1+3+4+2+1)*(3+4)=153
【数据规模】
对于全部的数据,保证有n<=10000。
标签
算法竞赛进阶指南
斜率优化dp入门题。
这道题需要简单的推一推式子。
我们用两个前缀和w[i],time[i]w[i],time[i]分别表示价值与时间的前缀和。
用f[i]f[i]表示前i个物品最优的安排需要的花费。
则有:
f[i]=min(f[j]+S∗(w[n]−w[j])+time[i]∗(w[i]−w[j]))f[i]=min(f[j]+S∗(w[n]−w[j])+time[i]∗(w[i]−w[j]))
其中我们使用了一种叫做费用提前计算的思想,把当前的启动时间对之后所有任务的影响都计算了出来。
但只推出这个式子每次转移是O(n)O(n)的,无法匹配这道题的数据范围。
于是我们将min去掉,变成:
=>f[i]=f[j]+S∗(w[n]−w[j])+time[i]∗(w[i]−w[j])f[i]=f[j]+S∗(w[n]−w[j])+time[i]∗(w[i]−w[j])
然后移个项:
=>f[j]=f[i]−S∗(w[n]−w[j])−time[i]∗(w[i]−w[j])f[j]=f[i]−S∗(w[n]−w[j])−time[i]∗(w[i]−w[j])
然后展开括号并整理式子:
=>f[j]=(S+time[i])∗w[j]+f[i]−S∗w[n]−time[i]∗w[i]f[j]=(S+time[i])∗w[j]+f[i]−S∗w[n]−time[i]∗w[i]
发现这是一个关于w[j]的一次函数,于是对于每一个jj我们对应成平面上的点(w[j],f[j])(w[j],f[j]),可以看到w[j]是单调递增的。
对于上面的式子,我们想要f[i]最小,就需要让这个一次函数的截距最小。
显然需要维护一个下凸壳,并且将过于平缓的线段(斜率小于S+time[j]S+time[j]的一定不如后面的线段优)删去就行了。
代码:
#include<bits/stdc++.h>
#define ll long long
#define N 10005
using namespace std;
inline ll read(){
ll ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
int n,S,q[N],hd,tl;
ll w[N],tim[N],f[N];
inline double slope(int x,int y){return (f[x]-f[y])/(w[x]-w[y]);}
int main(){
n=read(),S=read(),hd=1,tl=1;
for(int i=1;i<=n;++i)tim[i]=read()+tim[i-1],w[i]=read()+w[i-1];
for(int i=1;i<=n;++i){
while(hd<tl&&1.0*(S+tim[i])>slope(q[hd+1],q[hd]))++hd;
f[i]=f[q[hd]]+S*(w[n]-w[q[hd]])+tim[i]*(w[i]-w[q[hd]]);
while(hd<tl&&slope(q[tl],q[tl-1])>slope(i,q[tl]))--tl;
q[++tl]=i;
}
cout<<f[n];
return 0;
}