任务安排1 / 任务安排2 / 任务安排
题目链接:ybt金牌导航1-5-1 / ybt金牌导航1-5-2 / luogu P2365
题目大意
有一些任务,你可以把它分成连续的几段来完成。
对于每一段,要先让机器预热一个固定的时间,然后再完成。
下一段任务要在前面段的任务都完成了才可以开始热机。
这一批任务完成的时间都是他们之间的和,然后每个任务的费用是它完成的时刻乘它的费用系数。
让你找一种方案使得总费用最小,输出总费用。
思路
首先我们可以看出它直接做可以用 DP 来做。
我们用
f
i
f_i
fi 表示费用系数前缀和,
t
i
t_i
ti 表示时间前缀和,那我们可以列出方程。
d
p
i
=
m
i
n
j
=
1
i
−
1
{
d
p
j
+
(
f
i
−
f
j
)
×
t
i
+
S
×
(
f
n
−
f
j
)
}
dp_i=min_{j=1}^{i-1}\{dp_j+(f_i-f_j)\times t_i+S\times(f_n-f_j)\}
dpi=minj=1i−1{dpj+(fi−fj)×ti+S×(fn−fj)}
(如果是ybt上的任务安排1就直接用这个方程DP就好了, n 2 n^2 n2 能过的)
然后在这里似乎还是很迷茫,我们拆括号:
d
p
i
=
m
i
n
j
=
1
i
−
1
{
d
p
j
+
f
i
×
t
i
−
f
j
×
t
i
+
S
×
f
n
−
S
×
f
j
}
dp_i=min_{j=1}^{i-1}\{dp_j+f_i\times t_i-f_j\times t_i+S\times f_n-S\times f_j\}
dpi=minj=1i−1{dpj+fi×ti−fj×ti+S×fn−S×fj}
d
p
i
=
m
i
n
j
=
1
i
−
1
{
d
p
j
+
f
i
×
t
i
+
S
×
f
n
−
t
i
×
f
j
−
S
×
f
j
}
dp_i=min_{j=1}^{i-1}\{dp_j+f_i\times t_i+S\times f_n-t_i\times f_j-S\times f_j\}
dpi=minj=1i−1{dpj+fi×ti+S×fn−ti×fj−S×fj}
我们会发现
f
i
×
t
i
+
S
×
f
n
f_i\times t_i+S\times f_n
fi×ti+S×fn 在枚举
i
i
i 就确定了,剩下的一看,有关
j
j
j 的就是
d
p
j
,
f
j
dp_j,f_j
dpj,fj,发现它可以用斜率优化来做:
对于 a>b,如果 a 比 b 优:
d
p
a
+
f
i
×
t
i
+
S
×
f
n
−
t
i
×
f
a
−
S
×
f
a
≤
d
p
b
+
f
i
×
t
i
+
S
×
f
n
−
t
i
×
f
b
−
S
×
f
b
dp_a+f_i\times t_i+S\times f_n-t_i\times f_a-S\times f_a\leq dp_b+f_i\times t_i+S\times f_n-t_i\times f_b-S\times f_b
dpa+fi×ti+S×fn−ti×fa−S×fa≤dpb+fi×ti+S×fn−ti×fb−S×fb
d
p
a
−
t
i
×
f
a
−
S
×
f
a
≤
d
p
b
−
t
i
×
f
b
−
S
×
f
b
dp_a-t_i\times f_a-S\times f_a\leq dp_b-t_i\times f_b-S\times f_b
dpa−ti×fa−S×fa≤dpb−ti×fb−S×fb
d
p
a
−
d
p
b
≤
t
i
×
f
a
+
S
×
f
a
−
t
i
×
f
b
−
S
×
f
b
dp_a-dp_b\leq t_i\times f_a+S\times f_a-t_i\times f_b-S\times f_b
dpa−dpb≤ti×fa+S×fa−ti×fb−S×fb
d
p
a
−
d
p
b
≤
(
t
i
+
S
)
×
(
f
a
−
f
b
)
dp_a-dp_b\leq (t_i+S)\times (f_a-f_b)
dpa−dpb≤(ti+S)×(fa−fb)
d
p
a
−
d
p
b
f
a
−
f
b
≤
t
i
+
S
\dfrac{dp_a-dp_b}{f_a-f_b}\leq t_i+S
fa−fbdpa−dpb≤ti+S
然后你会发现右边的是满足单调性的。
那我们就可以用单调队列来搞,来确定下凸图里面哪个最优。
(就把左边不满足的全踢出去,剩下的最左的那个就是最优的)
然后就搞斜率优化就好了。
代码
#include<cstdio>
#include<cstring>
using namespace std;
int n, s, t[5001], f[5001];
int st[5001], sf[5001], l, r;
int dp[5001], q[5001], x[5001], y[5001];
int main() {
scanf("%d %d", &n, &s);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &t[i], &f[i]);
st[i] = st[i - 1] + t[i];//计算前缀和
sf[i] = sf[i - 1] + f[i];
}
memset(dp, 0x7f, sizeof(dp));
dp[0] = 0;
l = r = 1;
for (int i = 1; i <= n; i++) {
while (l < r && y[q[l + 1]] - y[q[l]] <= (st[i] + s) * (x[q[l + 1]] - x[q[l]])) l++;//因为具有单调性,我们可以直接把前面不优的线段割掉
dp[i] = dp[q[l]] + sf[i] * st[i] + s * sf[n] - st[i] * sf[q[l]] - s * sf[q[l]];//剩下的第一个就是最优的
x[i] = sf[i];//记录这个点
y[i] = dp[i];
while (l < r && (y[i] - y[q[r]]) * (x[q[r]] - x[q[r - 1]]) <= (y[q[r]] - y[q[r - 1]]) * (x[i] - x[q[r]])) r--;//按斜率维护下凸图
q[++r] = i;
}
printf("%d", dp[n]);
return 0;
}