为了更好的阅读体检,为了更好的阅读体检,,可以查看我的算法学习博客第四题-SSTF算法
在线评测链接:P1269
题目内容
塔子哥是一名磁盘维修师,他的工作是检查和修复损坏的磁盘。为了提高工作效率,他使用了一种最短服务时间优先(SSTF)算法来安排他的磁盘读取请求。每当他收到一个需要读取某个磁道上的数据的请求,他就把它加入到一个队列中。然后,他按照SSTF算法的规则,选择一个最适合当前磁头位置的请求来执行。这样,他可以减少磁头移动的距离,节省时间和能源。
现在,塔子哥想要知道,在一段时间内,他的磁头总共移动了多少个磁道。
给定 0 时刻的磁头位置和速度,以及随后的一系列请求,请你帮助塔子哥计算出这个数目。
SSTF算法的规则如下:每执行完一次磁盘读取后(读取时间忽略),检查当前时刻及其之前到达的所有请求,服务距离当前磁道最近的请求。当有多个磁头距离当前值较近时,服务磁道号较小的请求。若当前时刻及其之前到达的所有请求都已经处理,磁停留不动,等待下一个请求到来。
输入描述
输入第一行三个正整数 n,m,k ,表示磁盘请求次数为 n ,磁头每移动 1 个磁道花费的时间为 m ,磁头初始所在磁道号是 k 。
输入第二行 n 个正整数 ,表示第 i 次请求的磁道号。
输入第三行 n 个正整数 ,表示第 i 次请求的到达时间,不保证该序列递增。
对于所有的数据:,
,
。
输出描述
输出为一个整数,表示磁头移动的总磁道数。
样例
输入
5 1 5 3 4 1 8 5 10 4 9 7 8
输出
12
样例解释
0时刻进入等待状态至4时刻 当前请求队列为(数字代表磁道号):{ 4 }
4时刻 磁头从5号磁道 移动到 4号磁道 ,5时刻到达, 当前移动长度为1
5时刻进入等待状态至7时刻 当前请求队列为(数字代表磁道号):{8 }
7 时刻 磁头从4号磁道 移动到 8号磁道 ,11时刻到达, 当前移动长度为5 当前请求队列为(数字代表磁道号):{ 1\ 3\ 5}
11 时刻 磁头从8号磁道 移动到 5号磁道 ,14时刻到达, 当前移动长度为8 当前请求队列为(数字代表磁道号):{ 1\ 3 }
14 时刻 磁头从5号磁道 移动到 3号磁道 ,16时刻到达, 当前移动长度为10 当前请求队列为(数字代表磁道号):{1 }
16 时刻 磁头从3号磁道 移动到 1号磁道 ,18时刻到达, 当前移动长度为12
总移动长度为12
思路
贪心 + multiset
本题是一个看起来简单的模拟题,只需要考虑在当前时间和当前位置下,选择一个已经到来的请求,这个请求距离当前位置最近。
对于当前位置,如何维护一个有序序列,使得其可以快速获得已经到达的请求中,距离其最近的磁道呢?
multiset 上二分。
你需要考虑的是,每移动一个磁道需要花费 m 的时间,移动 k 次磁道就需要 km 的时间,你需要将这 km 内新的请求加入到 multiset 中,等待本次磁道移动结束后,从中挑选最近的新的请求对应的磁道。
但是实际上,如果一个任务的下一个任务所在磁道不变,其无需花费任何时间即可完成这个任务。故我们只需要一个 set 即可。
时间复杂度:
视频实况 v1, 65:04-97:03,以及视频实况 v2
类似题目推荐
set/multiset 练习题
-
美团 P1238. 2023.04.15-实习-第四题-俄罗斯套娃
贪心练习题
-
荣耀 P1040. 2022.9.22-破译数字密码
-
腾讯 P1005. 2022.10.16-汽车
LeetCode
set/multiset 练习题
-
327. 区间和的个数
-
218. 天际线问题
代码
CPP
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
set<int> S;
int a[N], t[N];
int idx[N];
int n, m, k;
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) scanf("%d", &t[i]);
for (int i = 1; i <= n; ++i) idx[i] = i;
// 按照请求到达的时间排序
sort(idx + 1, idx + n + 1, [&](const int x, const int y) {
return t[x] < t[y];
});
long long ans = 0;
// prep 表示当前的时间,小于等于 prep 的任务都已到达
long long prep = t[idx[1]];
for (int i = 1, cur = k; i <= n; ++i) {
int j = i;
// 将到达时间小于等于 prep 的任务都加入 S
while (j <= n && t[idx[j]] <= prep) j += 1;
for (int c = i; c < j; ++c) S.insert(a[idx[c]]);
// 判定下一个任务的到达时间,这之间的时间不会有任务到来了,可以安心选择
long long nxt = 1e18;
if (j <= n) nxt = t[idx[j]];
// 确定不会有新任务到来的时间长度
long long tim = nxt - prep;
while (!S.empty() && tim > 0) {
// 找到当前位置右边的,最靠近当前位置的任务
auto it = S.lower_bound(cur);
if (it == S.end()) {
// 此时右边没有,那么只能考虑左边了
it--;
ans += cur - *it;
tim -= 1ll * m * (cur - *it);
cur = *it;
S.erase(it);
} else {
// 如果右边存在,考虑左边是否存在
if (it == S.begin()) {
// 当前位置左边不存在任务
ans += *it - cur;
tim -= 1ll * m * (*it - cur);
cur = *it;
S.erase(it);
} else {
// 当前位置左边存在任务,比较左边最靠近当前位置和右边最靠近当前位置的两个任务谁更靠近当前位置,如果一样近,则选择左边的(磁道号更小)
auto pit = prev(it);
if (cur - *pit <= *it - cur) {
ans += cur - *pit;
tim -= 1ll * m * (cur - *pit);
cur = *pit;
S.erase(*pit);
} else {
ans += *it - cur;
tim -= 1ll * m * (*it - cur);
cur = *it;
S.erase(it);
}
}
}
}
// 如果 tim < 0,说明在时间限制最后一个任务结束后,时间已经超过 nxt,这 |tim| 长的时间内也可能有新的任务到达,也需要算上。
if (tim < 0) prep = nxt - tim;
// 如果 tim >= 0,则直接处理下一个到达的任务即可。
else prep = nxt;
i = j - 1;
}
printf("%lld\n", ans);
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int cur = sc.nextInt();
int[][] a = new int[n][2];
for (int i = 0; i < n; ++i) a[i][0] = sc.nextInt();
for (int i = 0; i < n; ++i) a[i][1] = sc.nextInt();
// 按照请求到达的时间排序
Arrays.sort(a, (x, y) -> Integer.compare(x[1], y[1]));
TreeSet<Integer> S = new TreeSet<>();
long ans = 0;
// prep 表示当前的时间,小于等于 prep 的任务都已到达
long prep = a[0][1];
int i = 0;
while (i < n || !S.isEmpty()) {
// 将小于等于当前时间的请求全部加入
while (i < n && a[i][1] <= prep) {
S.add(a[i][0]);
i += 1;
}
// 判定下一个任务的到达时间,这之间的时间不会有任务到来了,可以安心选择
long nxt = (long) 1e18;
if (i < n) nxt = a[i][1];
// 确定不会有新任务到来的时间长度
long tim = nxt - prep;
while (!S.isEmpty() && tim > 0) {
// 找到当前位置右边的,最靠近当前位置的任务
Integer it = S.ceiling(cur);
if (it == null) {
// 此时右边没有,那么只能考虑左边了
it = S.last();
ans += cur - it;
tim -= (long) m * (cur - it);
cur = it;
S.remove(it);
} else {
// 如果右边存在,考虑左边是否存在
if (it.equals(S.first())) {
// 当前位置左边不存在任务
ans += it - cur;
tim -= (long) m * (it - cur);
cur = it;
S.remove(it);
} else {
// 当前位置左边存在任务,比较左边最靠近当前位置和右边最靠近当前位置的两个任务谁更靠近当前位置,如果一样近,则选择左边的(磁道号更小)
Integer pit = S.lower(it);
if (cur - pit <= it - cur) {
ans += cur - pit;
tim -= (long) m * (cur - pit);
cur = pit;
S.remove(pit);
} else {
ans += it - cur;
tim -= (long) m * (it - cur);
cur = it;
S.remove(it);
}
}
}
}
// 如果 tim < 0,说明在时间限制最后一个任务结束后,时间已经超过 nxt,这 |tim| 长的时间内也可能有新的任务到达,也需要算上。
if (tim < 0) prep = nxt - tim;
// 如果 tim >= 0,则直接处理下一个到达的任务即可。
else prep = nxt;
}
System.out.println(ans);
}
}



















