题目
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2726
\(n\) 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。
从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。
每个任务的费用是它的完成时刻乘以一个费用系数 \(f_i\)。请确定一个分组方案,使得总费用最小。
\(n\leq 3\times 10^5\)。
思路
斜率优化板子题。
先考虑 \(O(n^2)\) 怎么做。设 \(c[i],t[i]\) 分别为时间、费用的前缀和,如果没有启动时间的要求,设 \(f[i]\) 表示完成前 \(i\) 个任务所需的最小费用。那么有
那么有了启动时间的要求,我们考虑在将 \((j,i]\) 分为一组的时候,\((j,n]\) 的任务都需要等待 \(s\) 的单位时间。所以有
考虑优化上述方程。斜率优化是对于方程中含有与 \(i,j\) 相关的单项式,将方程化为 \(y=kx+b\) 的形式,其中 \(y=f[j]\),\(kx=\) 含 \(i,j\) 的项,\(b=\) 常数以及其他项。\(kx\) 中 \(k\) 表示含 \(i\) 的项,\(x\) 为 含 \(j\) 的项。
那么在把 \(\min\) 去掉后,上述方程可以写作
在这个一次函数中,斜率 \(t[i]+s\) 是一个定值,每一个二元组 \((c[j],f[j])\) 看做平面直角坐标系中的一个点,我们的目的是要最小化 \(f[i]\),其实也就是最小化这个函数的截距。
那么我们试想一条斜率固定的直线,从最下面开始往上平移,为了使截距最小,显然选择的决策点是往上平移碰到的第一个点。
显然这个点在所有决策点所构成的下凸壳中,所以我们可以用单调栈维护所有决策点构成的下凸壳,画图很好看出,直线往上平移最先碰到的点是下凸壳中第一个斜率比直线斜率 \((t[i]+s)\) 大的线段的左端点。
考虑到下凸壳中线段的斜率递增,可以二分找到这个最优决策点。
时间复杂度 \(O(n\log n)\)。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll N=300010;
ll n,s,top,q[N],t[N],c[N],f[N];
ll binary(ll i)
{
ll l=0,r=top-1,mid;
while (l<=r)
{
mid=(l+r)>>1;
ll x=q[mid],y=q[mid+1];
if (f[y]-f[x]>=(t[i]+s)*(c[y]-c[x])) r=mid-1;
else l=mid+1;
}
return q[r+1];
}
int main()
{
scanf("%lld%lld",&n,&s);
for (ll i=1;i<=n;i++)
{
scanf("%lld%lld",&t[i],&c[i]);
t[i]+=t[i-1]; c[i]+=c[i-1];
}
for (ll i=1;i<=n;i++)
{
ll j=binary(i);
f[i]=f[j]+(c[i]-c[j])*t[i]+(c[n]-c[j])*s;
if (top)
for (ll y=q[top],x=q[top-1];top;y=q[top],x=q[top-1])
if (top && (f[y]-f[x])*(c[i]-c[y])>=(f[i]-f[y])*(c[y]-c[x])) top--;
else break;
q[++top]=i;
}
printf("%lld",f[n]);
return 0;
}