题目描述
有 N N N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。
机器会把这 N N N 个任务分成若干批,每一批包含连续的若干个任务。
从时刻 0 0 0开始,任务被分批加工,执行第 i i i 个任务所需的时间是 T i T_i Ti。
另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S S S 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。
也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 C i C_i Ci。
请为机器规划一个分组方案,使得总费用最小。
输入格式
第一行包含整数 N N N。
第二行包含整数 S S S。
接下来N行每行有一对整数,分别为 T i T_i Ti 和 C i C_i Ci,表示第 i i i 个任务单独完成所需的时间 T i T_i Ti 及其费用系数 C i C_i Ci。
输出格式
输出一个整数,表示最小总费用。
数据范围
1
≤
N
≤
3
×
1
0
5
1≤N≤3\times10^5
1≤N≤3×105 ,
1
≤
T
i
,
C
i
≤
512
1≤T_i,C_i≤512
1≤Ti,Ci≤512,
−
512
≤
S
≤
512
-512≤S≤512
−512≤S≤512
输入样例
5
1
1 3
3 2
4 3
2 3
1 4
输出样例
153
算法思想
在任务安排2中斜率 k ( S u m T i + S k (Sum_{T_i}+S k(SumTi+S)是单调递增的,向后计算时新加入点的 x ( S u m C j ) x(Sum_{C_j}) x(SumCj)坐标也是单调递增的,那么可以使用队列存储所有计算过的点:
- 在查询时,可以将队头小于等于当前斜率的点全部出队,即 f h h + 1 − f h h S u m C h h + 1 − S u m C h h ≤ S u m T i + S \frac{f_{hh+1}-f_{hh}}{Sum_{C_{hh+1}}-Sum_{C_{hh}}}\le Sum_{T_i}+S SumChh+1−SumChhfhh+1−fhh≤SumTi+S。因为斜率单调递增,后面只会越来越大。
- 在插入时,可以将队尾大于等于当前斜率的点(不在凸包上的点)出队,即 f t t − f t t − 1 S u m C t t − S u m C t t − 1 ≥ f i − f t t S u m C i − S u m C t t \frac{f_{tt}-f_{tt-1}}{Sum_{C_{tt}}-Sum_{C_{tt-1}}}\ge \frac{f_i-f_{tt}}{Sum_{C_i}-Sum_{C_{tt}}} SumCtt−SumCtt−1ftt−ftt−1≥SumCi−SumCttfi−ftt。因为不在凸包上,意味着不可能有最小值。
对比任务安排2,本题中 S S S的数据范围发生了变化( − 512 ≤ S ≤ 512 -512≤S≤512 −512≤S≤512),那么斜率 k ( S u m T i + S k (Sum_{T_i}+S k(SumTi+S)不满足单调性质,因此在查询时不能将队头出队。为了提高查询效率,可以使用二分搜索来确定第一个大于当前斜率的点在队列中的位置。因此:
- 在查询时,使用二分搜索,找到第一个 f m i d + 1 − f m i d S u m C m i d + 1 − S u m C m i d > S u m T i + S \frac{f_{mid+1}-f_{mid}}{Sum_{C_{mid+1}}-Sum_{C_{mid}}}\gt Sum_{T_i}+S SumCmid+1−SumCmidfmid+1−fmid>SumTi+S。 m i d mid mid表示符合条件的点在队列中的位置。
- 在插入时,可以将队尾大于等于当前斜率的点(不在凸包上的点)出队,即 f t t − f t t − 1 S u m C t t − S u m C t t − 1 ≥ f i − f t t − 1 S u m C i − S u m C t t − 1 \frac{f_{tt}-f_{tt-1}}{Sum_{C_{tt}}-Sum_{C_{tt-1}}}\ge \frac{f_i-f_{tt-1}}{Sum_{C_i}-Sum_{C_{tt-1}}} SumCtt−SumCtt−1ftt−ftt−1≥SumCi−SumCtt−1fi−ftt−1。因为不在凸包上,意味着不可能有最小值。
注意:这里之所以使用二分搜索来查找,是因为队列中只维护了凸包边界上的点,而凸包的斜率一定是单调。
时间复杂度
在每个任务中都需要进行二分搜索,所以时间复杂度为 O ( n l o g ) O(nlog) O(nlog)。
代码实现
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 300010;
LL t[N], c[N], f[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;
q[tt] = 0; // 将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]])) r = mid;
else l = mid + 1;
}
int j = q[r];
//计算f[i]
f[i] = f[j] + t[i] * (c[i] - c[j]) + s * (c[n] - c[j]);
//队列中至少保留两个点,并且将队尾不在凸包上的点出队
//为了避免long long 溢出,这里强转为double进行大小比较
while(hh < tt && (double)(f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (double)(f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt --;
q[++ tt] = i;
}
printf("%lld\n", f[n]);
return 0;
}