【ybt金牌导航1-5-3】【luogu P5785】任务安排3 / 任务安排

78 篇文章 2 订阅
9 篇文章 0 订阅

任务安排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 fafbdpadpbti+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值