题目描述
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。从时刻 0 开始,任务被分批加工,执行第i个任务所需的时间是 Ti 。另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。也就是说,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 Ci 。
请为机器规划一个分组方案,使得总费用最小。
输入
第一行是 N。第二行是 S。
下面 N 行每行有一对正整数,分别为 Ti 和 Ci ,表示第 i 个任务单独完成所需的时间是 Ti 及其费用系数 Ci 。
输出
一个数,最小的总费用。
样例输入
5 1 1 3 3 2 4 3 2 3 1 4
样例输出
153
提示
【样例说明】
分组方案为 {1,2},{3},{4,5},则完成时间为 {5,5,10,14,14},费用 C={15,10,30,42,56},总费用为 153。
【数据范围与提示】
对于全部数据,1≤N≤,0≤S≤50,1≤Ti ,Ci ≤100。
题解
作为第一道斜率优化,此处详细讲讲(包括思路)。
根据样例,很容易写出dp转移方程:
其中sumt表示时间的前缀和,sumc表示费用的前缀和。注意上面dp方程中的第三项表示此处启动机器花费时间S,整个时间轴(时刻)都要向右移S。
这样做是的效率,只能拿暴力分。
注意这个转移式子是求全局的min,因此考虑维护一个队列,想办法直接取出队首。观察出这个式子去掉min(也就是说取每一个点),移项后得到:
如果把dp[j]作为y,把(sumt[i]+S)作为k,把sumc[j]作为x,后面的部分全部作为b。
那么这就是一个一次函数表达式。假设此时已经枚举到了第i个,那么显然此时的sumt[i],sumc[i]都是已知,如果找到了一个j<i,那么关于j的所有信息:dp[j],sumt[j],sumc[j]都是已知。相当于只有dp[i]未知。注意斜率k只与i有关,相当于对于任意的j,斜率是不变的。根据上述一次函数式子,每一个j都可以对应成一个点,由于k单调递增,x单调递增(显然),所以b越小,dp[i]越小。就是说把1到j所有的点标记在笛卡尔坐标系中,想象一条斜率确定且递增的线,对于每一个点能求出一个b,就是找b最小的那个点。这大概就是斜率优化的优化思想。
我们需不需要一个一个的去试?那样做是O(n)的。如果说有一些点已经在坐标系中,是否存在情况使得这个点与线相交的b都不可能是最小的?(注意,对于每一个i,线的斜率会改变)这样就可以直接“出队”,那么剩下的一定是可能作为答案的点。
从根本入手,k和x都是正数且递增,那么随着枚举的i的增加,点的横纵坐标会增加(这是显然的),同时线的斜率递增。可以把点看成一个图形,可以找到一个下边界,每次从下面移动直线时都只可能会和下边界的某一个点相交作为答案,相当于下边界以上的点无论如何不可能作为答案,那么就可以出队了。同时,由于相切时才能得到最优答案,如果有两点构成直线的斜率大于等于当前i的斜率,那么由于线的斜率单调递增,这两个点可能会作为后面的答案;反之,如果此处两个点的斜率小于k,根据线的斜率递增的性质,无论如何左边的这个点不可能再作为答案了,因此可以出队。至此,斜率优化基本的两大操作已经讲完。
由于这是一个下凸包,所求的点的b越小越好,也就是说这个点越低越好,因此如果从1到n入队的话,编号越前的点越优,因此在清理完操作二后,直接取队首作为答案即可。这和斜率有什么关系?首先线的斜率随着i的增大而增大,而判断队首是否仍然符合条件的方式就是判断队首和队首+1的点所构成的斜率是不是小于线的斜率。同样的,为了维护一个下凸包,每次把i入队之前都要看队尾和队尾-1构成的斜率是否大于队尾和i构成的斜率。
因此,对于最基本的斜率优化,通过普通的dp转移方程,找到自变量和因变量,然后维护凸包即可。
参考代码
#include<cstdio>
#include<cstring>
#define MAXN 300100
#define LL long long
using namespace std;
LL n,s,t[MAXN],c[MAXN],dp[MAXN],sumt[MAXN],sumc[MAXN];
LL q[MAXN],head=1,tail=0;
LL max1(LL p,LL q) { return p>q?p:q; }
LL min1(LL p,LL q) { return p<q?p:q; }
int main()
{
memset(dp,0x3f,sizeof(dp));
scanf("%lld%lld",&n,&s);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&t[i],&c[i]);
sumt[i]=sumt[i-1]+t[i];
sumc[i]=sumc[i-1]+c[i];
}
q[++tail]=0;dp[0]=0;
for(int i=1;i<=n;i++)
{
while(head<tail&&(dp[q[head+1]]-dp[q[head]])<=(sumt[i]+s)*(sumc[q[head+1]]-sumc[q[head]])) head++;
dp[i]=dp[q[head]]+sumt[i]*(sumc[i]-sumc[q[head]])+s*(sumc[n]-sumc[q[head]]);
while(head<tail&&(dp[q[tail]]-dp[q[tail-1]])*(sumc[i]-sumc[q[tail-1]])>=(dp[i]-dp[q[tail-1]])*(sumc[q[tail]]-sumc[q[tail-1]])) tail--;
q[++tail]=i;
}
printf("%lld",dp[n]);
return 0;
}