任务安排3 / 任务安排
题目链接:ybt金牌导航1-5-3 / luogu P5785
题目大意
有一些任务,你可以把它分成连续的几段来完成。
对于每一段,要先让机器预热一个固定的时间,然后再完成。
下一段任务要在前面段的任务都完成了才可以开始热机。
这一批任务完成的时间都是他们之间的和,然后每个任务的费用是它完成的时刻乘它的费用系数。
让你找一种方案使得总费用最小,输出总费用。
(机器完成的时间可能会是负数)
思路
不会机器完成时间是非负数的先看这个:
——>点我跳转<——
接着我们来说说是时间有负数怎么搞。
那时间是负数有什么影响呢?当然就是这个式子中右边不再单调递增:
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>
#define ll long long
using namespace std;
ll n, s, t[300001], f[300001];
ll st[300001], sf[300001], best, l, r;
ll dp[300001], q[300001], x[300001], y[300001];
int get_best(int i) {
int lef = 1, rig = r, re = 1;
while (lef <= rig) {
int mid = (lef + rig) >> 1;
if (y[q[mid + 1]] - y[q[mid]] <= (st[i] + s) * (x[q[mid + 1]] - x[q[mid]])) {
lef = mid + 1;
}
else rig = mid - 1, re = mid;
}
return re;
}
int main() {
scanf("%lld %lld", &n, &s);
for (ll i = 1; i <= n; i++) {
scanf("%lld %lld", &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 = 1;
r = 1;
for (ll 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++;
best = get_best(i);//改用二分
dp[i] = dp[q[best]] + sf[i] * st[i] + s * sf[n] - st[i] * sf[q[best]] - s * sf[q[best]];
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("%lld", dp[n]);
return 0;
}