题目描述:
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。
机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。
从时刻0开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。
另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。
也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 Ci。
请为机器规划一个分组方案,使得总费用最小。
输入格式
第一行包含整数 N。
第二行包含整数 S。
接下来N行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci。
输出格式
输出一个整数,表示最小总费用。
数据范围
1≤N≤5000,
0≤S≤50,
1≤Ti,Ci≤100
输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153
分析:
题目意思是完成每个任务需要一定的时间ti和费用ci,将任务分成若干批,每批任务都需要加上启动时间S,完成这批任务的花费是这批任务总的费用乘上所有任务都结束的时间。我们可以很容易的预处理出完成任务时间和费用的前缀和,但是启动时间却不好统计,因为我们不知道需要将任务分成几批。分析可得,完成一批任务时,消耗的时间由完成各个任务的时间加上启动机器带来的时间组成。设完成第一批任务耗时T1(不包括启动时间),费用和是C1,则完成这批任务消耗的总费用等于(T1 + S) * C1 = T1 * C1 + S * C1,同理,完成第二批任务的总费用等于(T1 + S + T2 + S) * C2,可以发现,每启动一次机器消耗的时间,不仅对当前任务增加了S * Ci的费用,对后面所有批次的任务都增加了S * C的费用,我们将启动一次机器对总费用的影响都算在了这一批次的花费中,即求第一批任务的花费时,就写成T1 * C1 + S * (C1 + ... + Cn),然后计算第二批任务的花费时,写成(T1 + T2) * C2 + S * (C2 + ... + Cn),这样就相当于将完成任务的花费和启动机器的花费分开处理了,这一技巧很重要。
状态表示f[i]表示完成前i个任务的最小花费,则我们需要找到上一批次任务是何时完成的,上一批次的最后一个任务可以是i - 1,i - 2,...,0,其中0表示i前面所有的都是一批次的,状态转移方程为f[i] = min(f[j] + (sc[i] - sc[j])*st[i] + (sc[n] - sc[j])*S),其中sc和st分别表示完成任务的花费和时间的前缀和。f[j]是完成前j个任务的花费,从j+1到i就是本批次的任务了,(sc[i] - sc[j])*st[i]是本批次任务的花费(不包括启动时间),我们不知道前面一共完成了多少批次的任务,所以不知道确切的第i个任务完成的时间,但是既然可以将任务完成时间和机器启动时间分开算,这时我们就只需要考虑当前批次启动机器的时间S对后面所有任务增加的花费是多少了,也就是这次启动机器带来花费的增加量是(sc[n] - sc[j])*S,从而推出了上面的状态转移方程。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5005;
int f[N],t[N],c[N];
int main(){
int n,s;
scanf("%d%d",&n,&s);
for(int i = 1;i <= n;i++){
scanf("%d%d",&t[i],&c[i]);
t[i] += t[i-1],c[i] += c[i-1];
}
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] + t[i] * (c[i] - c[j]) + s * (c[n] - c[j]));
}
}
printf("%d\n",f[n]);
return 0;
}