首页 > 技术文章 > 【题解】P2365 任务安排

ZhengkunJia 2020-09-09 16:58 原文

P2365 任务安排

题目描述

nnn 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 nnn 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 iii 个任务单独完成所需的时间为 tit_iti。在每批任务开始前,机器需要启动时间 sss,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 fif_ifi。请确定一个分组方案,使得总费用最小。

输入格式

第一行一个正整数 nnn。
第二行是一个整数 sss。

下面 nnn 行每行有一对数,分别为 tit_iti 和 fif_ifi,表示第 iii 个任务单独完成所需的时间是 tit_iti 及其费用系数 fif_ifi。

输出格式

一个数,最小的总费用。

输入输出样例

输入 #1

5
1
1 3
3 2
4 3
2 3
1 4

输出 #1

153

说明/提示

【数据范围】
对于 100%100%100% 的数据,1≤n≤50001\le n \le 50001≤n≤5000,0≤s≤500 \le s \le 500≤s≤50,1≤ti,fi≤1001\le t_i,f_i \le 1001≤ti​,fi​≤100。

【样例解释】
如果分组方案是 {1,2},{3},{4,5}{1,2},{3},{4,5}{1,2},{3},{4,5},则完成时间分别为 {5,5,10,14,14}{5,5,10,14,14}{5,5,10,14,14},费用 C=15+10+30+42+56C=15+10+30+42+56C=15+10+30+42+56,总费用就是 153153153。

将前\(i\)个任务分成\(j\)次的花费:

\[pay[i][times] = min(pay[j][times - 1] + (t[i] - t[j] + s * times) * (f[i] - f[j])) \]

需要知道机器启动了多少次

费用提前计算:

\[pay[i] = min(pay[j] + t[i] * (f[i] - f[j]) + s * (f[n] - f[j])) \]

\[pay[i] = pay[j] + t[i] * f[i] - t[i] * f[j] + s * f[n] - s * f[j] \]

\[pay[j] = (t[i] + s)* f[j] + t[i] * f[i] - s * f[n] + pay[i] \]

\[x = f[j]; y = pay[j]; t[i] + s = k; pay[i] - s * f[n] = b \]

当我们的目标是求最小截距的时候,想象为一条线从下往上平移,第一次接触到的点截距最小。我们要寻找这个“最下面的”点,需要维护下凸包,通过斜率来判断点。

因为\(k = t[i] + s\)具有单调性,也就是说那条从下往上平移的线的斜率随着\(i\)的递推是逐渐增大的,而我们要寻找的那个”最下面“的点的横坐标也一定是随着\(i\)的地推逐渐增大的,为了及时排除无用状态,使用一个队列。

初始化:que[0] = 0

添加新的决策集:将\((f[i], pay[i])\)入队并不断判断队尾三个元素的凸包性质,即若不满足三个点连出的两条线的斜率递增,则删除中间点。

#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n, s, tt, ff;
ll pay[300010], t[300010], f[300010];

double slope(int a, int b){
	return 1.0 * (pay[b] - pay[a]) / (f[b] - f[a]);
}
int que[300010];
int h, tail;
int main(){
	scanf("%d%d", &n, &s);
	for(int i = 1; i <= n; i++){
		scanf("%d%d", &tt, &ff);
		t[i] = t[i - 1] + tt;
		f[i] = f[i - 1] + ff;
	}
	h = tail = 1;
	que[1] = 0;
	for(int i = 1; i <= n; i++){
		while(h < tail && pay[que[h + 1]] - pay[que[h]] < (t[i] + s) * (f[que[h + 1]] - f[que[h]])) h++;
		
		pay[i] = pay[que[h]] + s * (f[n] - f[que[h]]) + t[i] * (f[i] - f[que[h]]);
		
		while(h < tail && (pay[que[tail]] - pay[que[tail - 1]]) * (f[i] - f[que[tail]]) > (pay[i] - pay[que[tail]]) * (f[que[tail]] - f[que[tail - 1]]))	tail--;
		que[++tail] = i;
	}
	printf("%lld\n", pay[n]);
	return 0;
}

推荐阅读