题目描述:
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。
机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。
从时刻0开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。
另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。
也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 Ci。
请为机器规划一个分组方案,使得总费用最小。
输入格式
第一行包含两个整数 N 和 S。
接下来N行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci。
输出格式
输出一个整数,表示最小总费用。
数据范围
1≤N≤3∗10^5
0≤S,Ci≤512
−512≤Ti≤512
输入样例:
5 1
1 3
3 2
4 3
2 3
1 4
输出样例:
153
分析:
在AcWing 301 任务安排2中,我们给出了任务安排问题的斜率优化的解法,因为斜率k = st[i] + S是单调递增的,所以队列中小于当前k的斜率一定小于后面的k,于是我们在查找第一个大于k的斜率时将小于k的斜率都删除了,从而保证了O(n)的时间复杂度。但是本题的Ti可能是负数,尽管时间是负数设置得有些不合理,我们只能暂且认为它是合理的,Ti是负数时,k = st[i] + S就未必单调递增了,因为前缀和不一定单调递增了,这意味着,后面的k可能小于之前的k,我们不应该在寻找第一个大于k的斜率时删掉小于k的斜率了,因为后面的k还可能用到,但是不删除小于k的斜率,意味着每次寻找j都需要从堵头找起,时间复杂度最坏可能是平方级别的,为此我们在找第一个大于k的斜率时,只能对队列中的斜率做二分查找,用O(nlogn)的时间复杂度去解决本题了。本题的其它代码与上题一致,故分析过程可以参考上题,唯一修改的地方就是将原来出队头的代码修改为二分找j的代码了。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 300005;
typedef long long ll;
ll f[N],t[N],c[N];
int q[N];
int main(){
int n,s;
scanf("%d%d",&n,&s);
for(int i = 1;i <= n;i++){
scanf("%lld%lld",&t[i],&c[i]);
t[i] += t[i-1],c[i] += c[i-1];
}
int hh = 0,tt = 0;
for(int i = 1;i <= n;i++){
int l = hh,r = tt;
while(l < r){
int mid = l + r >> 1;
if(f[q[mid+1]]-f[q[mid]]<=(t[i]+s)*(c[q[mid+1]]-c[q[mid]])) l = mid + 1;
else r = mid;
}
f[i] = f[q[l]] - (t[i] + s) * c[q[l]] + c[i] * t[i] + s * c[n];
while(hh < tt && (f[q[tt]]-f[q[tt - 1]])*(c[i]-c[q[tt]]) >= (f[i]-f[q[tt]])*(c[q[tt]]-c[q[tt - 1]])) tt--;
q[++tt] = i;
}
printf("%lld\n",f[n]);
return 0;
}