【备战秋招】每日一题:4月29日美团春招:题面+题目思路 + C++/python/js/Go/java带注释

2023大厂笔试模拟练习网站(含题解)
www.codefun2000.com
最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据,挂载到我们的OJ上,供大家学习交流,体会笔试难度。现已录入200+道互联网大厂模拟练习题,还在极速更新中。欢迎关注公众号“塔子哥学算法”获取最新消息。

提交链接: 

https://codefun2000.com/p/P1138

为了更好的阅读体检,可以查看OJ上的题解。进入提交链接,点击右边菜单栏的"查看塔子哥的题解"

在线评测链接:P1269

题目内容

塔子哥是一名磁盘维修师,他的工作是检查和修复损坏的磁盘。为了提高工作效率,他使用了一种最短服务时间优先(SSTF)算法来安排他的磁盘读取请求。每当他收到一个需要读取某个磁道上的数据的请求,他就把它加入到一个队列中。然后,他按照SSTF算法的规则,选择一个最适合当前磁头位置的请求来执行。这样,他可以减少磁头移动的距离,节省时间和能源。

现在,塔子哥想要知道,在一段时间内,他的磁头总共移动了多少个磁道。

给定 0 时刻的磁头位置和速度,以及随后的一系列请求,请你帮助塔子哥计算出这个数目。

SSTF算法的规则如下:每执行完一次磁盘读取后(读取时间忽略),检查当前时刻及其之前到达的所有请求,服务距离当前磁道最近的请求。当有多个磁头距离当前值较近时,服务磁道号较小的请求。若当前时刻及其之前到达的所有请求都已经处理,磁停留不动,等待下一个请求到来。

输入描述

输入第一行三个正整数 n,m,k ,表示磁盘请求次数为 n ,磁头每移动 1 个磁道花费的时间为 m ,磁头初始所在磁道号是 k 。

输入第二行 n 个正整数a_i ,表示第 i 次请求的磁道号。

输入第三行 n 个正整数 t_i,表示第 i 次请求的到达时间,不保证该序列递增。

对于所有的数据:1\le n\le 10^51\le a_i,t_i\le 10^9 , 1 \leq m \leq 100

输出描述

输出为一个整数,表示磁头移动的总磁道数。

样例

输入

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 即可。

时间复杂度:O(n\log n)

视频实况 v1, 65:04-97:03,以及视频实况 v2

类似题目推荐

set/multiset 练习题

贪心练习题

LeetCode

set/multiset 练习题

代码

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值