看到这道似曾相识的题,心中顿时产生无限纠结~先看一下题目。
【题目描述】
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。【输入格式】
第一行是N(1<=N<=5000)。
第二行是S(0<=S<=50)。
下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。【输出格式】
一个数,最小的总费用。
【样例输入】
5
1
1 3
3 2
4 3
2 3
1 4
【样例输出】
153
做过APIO2010题目的同学都知道,这道题是APIO2010第一题特别行动队的简化版(题目本来出自POJ1180,原题数据范围是10,000),WS的XHJ竟然把数据范围定成了1,000,000…其实无论是POJ上的还是APIO上的数据范围都是要加斜率优化的,我曾抱着侥幸的想法到POJ上提交了一下——Time Limit Exceeded~n2的代码就是弱小啊。
不过在NOIP前我没有打算看斜率优化,现在就把这个n2的代码贴上来吧~
参考代码:
1 program batch;
2 var
3 n,i,j,k,s:integer;
4 min,ta,tc:longint;
5 t,f:array[0..5000]of longint;
6 c:array[0..5000]of longint;
7 begin
8 readln(n);
9 readln(s);
10 for i:=1 to n do
11 readln(t[i],f[i]);
12 for i:=1 to n do
13 begin
14 t[i]:=t[i]+t[i-1]; //累加时间
15 f[i]:=f[i-1]+f[i]; //累加单价
16 c[i]:=2147483647; //c数组初始化
17 end;
18 for i:=1 to n do
19 for j:=1 to i do
20 begin
21 ta:=f[n]-f[j-1];
22 tc:=f[i]-f[j-1];
23 min:=c[j-1]+s*ta+t[i]*tc; //计算当前分组下的花费
24 if c[i]>min then c[i]:=min; //记录最优解
25 end;
26 writeln(c[n]);
27 end.
首先要注意题目信息,序列的顺序不能改变。这样极大地方便了我们的分组。显然每项工作的输出时间=前一组工作时刻+调整时间+完成第i组所有工作的用时。我们看出,每项工作的输出时间与前面的工作时间有关。所以我们只用从前向后更新状态,在i指针前面枚举分组的断点即可。
其中,c[i]表示完成工作1到工作I的费用+因增加S从I到N增加的费用的总和的最小费用;t[i]表示完成工作i的时刻(注意是i之前包括i在内的工作时间的累加);f[i]表示工作1到i的单位费用的累加。
状态转移方程: c[i]=min{c[k-1]+s*(f[n]-f[k-1])+t[i]*(f[i]-f[k-1])}
k是分组断点的指针,因为i之前的c数组已经是最优,所以c[k-1]可以直接调用。s*(f[n]-f[k-1])代表了增加开机时间所花费的代价,t[i]*(f[i]-f[k-1])代表完成k到i的分组所需的费用。这样,c[i]的数值就是所有分组方案中花费最小的那个了。
(Saltless原创,转载请注明出处)