通过的第一道斜率优化
注:这并不是一篇教你具体怎样斜率优化的题解
因为我觉得太麻烦
题目描述
机器上有 n 个需要处理的任务,它们构成了一个序列。这些任务被标号为 1 到 n,因此序列的排列为 1,2,3⋯n。这 n 个任务被分成若干批,每批包含相邻的若干任务。从时刻 0 开始,这些任务被分批加工,第 i 个任务单独完成所需的时间是 Ti 。在每批任务开始前,机器需要启动时间 s,而完成这批任务所需的时间是各个任务需要时间的总和。
注意,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 Ci 。
请确定一个分组方案,使得总费用最小。
解题思路
首先可以列出一个很显然的DP式
设
F
[
i
]
[
j
]
F[i][j]
F[i][j]表示将前
i
i
i 个任务分成
j
j
j 批的最小花费
F
[
i
]
[
j
]
=
m
i
n
(
F
[
k
]
[
]
j
−
1
]
+
(
S
×
j
+
s
u
m
T
[
i
]
)
×
(
s
u
m
C
[
i
]
−
s
u
m
C
[
k
]
)
)
F[i][j]=min(F[k][]j-1]+(S\times j+sumT[i])\times (sumC[i]-sumC[k]))
F[i][j]=min(F[k][]j−1]+(S×j+sumT[i])×(sumC[i]−sumC[k]))
其中sum分别为前缀和
但是这个式子很不优美,而且是二维的不好优化
所有我们把第二维删掉,但是这样就计算不了S了,但是我们可以变一下,改成S对后面任务的贡献
设
F
[
i
]
F[i]
F[i]表示以第
i
i
i各任务为最后一个任务结尾的最小值
则
F
[
i
]
=
m
i
n
(
F
[
j
]
+
s
u
m
T
[
i
]
×
(
s
u
m
C
[
i
]
−
s
u
m
C
[
j
]
)
+
S
×
(
s
u
m
C
[
N
]
−
s
u
m
C
[
j
]
)
)
F[i]=min(F[j]+sumT[i]\times (sumC[i]-sumC[j])+S\times (sumC[N]-sumC[j]))
F[i]=min(F[j]+sumT[i]×(sumC[i]−sumC[j])+S×(sumC[N]−sumC[j]))
这就是费用提前计算,也是常用的DP方式
建议认真理解之后再往后看
但是这还是
O
(
n
2
)
O(n^2)
O(n2)的,不行
所以就斜率优化就可以了
但是这个T可能不是单调递增的这就意味着我们不能用单调队列,而是要维护一个下凸壳,每次用二分查找转移点
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
const LL N = 4e5+7;
LL F[N],T[N],C[N],sumT[N],sumC[N],n;
LL X(LL p)
{
return sumC[p];
}
LL Y(LL p)
{
return F[p];
}
db slope(LL a,LL b)
{
return 1.000*(Y(a)-Y(b))/(X(a)-X(b));
}
LL q[N];
LL S;
LL search(db k,LL L,LL R)
{
if(L==R) return q[L];
LL l=L,r=R;
while(l<r)
{
LL mid=(l+r)>>1;
if(slope(q[mid+1],q[mid])<=k) l=mid+1;
else r=mid;
}
return q[l];
}
int main()
{
scanf("%lld%lld",&n,&S);
for(LL 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];
}
LL l=1,r=1;
for(LL i=1;i<=n;i++)
{
LL j=search(S+sumT[i]*1.0000,l,r);
F[i]=F[j]-(S+sumT[i])*sumC[j]+sumT[i]*sumC[i]+S*sumC[n];
while(l<r&&slope(q[r],q[r-1])>=slope(i,q[r-1])) r--;
q[++r]=i;
}
cout<<F[n];
return 0;
}