题目背景

有 $n$ 个同学(从 $0$ 开始编号)在学习信奥课,同学们排队向老师提问。每个同学问的问题不同,因此答疑时长不同,设第 $i$ 个同学的答疑时长为 $t_i$;每个同学的耐心值也不同,设第 $i$ 个同学的耐心为 $p_i$。 如果一个同学等待太久,他会暴躁。每个同学的暴躁程度 $g_i$ 等于排在他前面的同学的答疑时长之和与自身耐心的差值,即:$g_i = \sum_{j=0}^{i-1}t_j - p_i$。 如果同学们很暴躁,老师会狂暴。老师的狂暴程度 $r$ 等于所有同学暴躁程度 $g_i$ 的最大值,即:$r = \max_{i=0}^{n-1}g_i$。 改变 $n$ 个同学的排队顺序,老师的狂暴程度可能会发生变化。求所有的排队顺序中,老师的狂暴程度的最小值 $\min r$。

题目描述

输入格式

从标准输入读入数据。 第一行为一个正整数 $n$($1 \le n \le 1,000$),表示有 $n$ 位同学。 第二行到第 $n+1$ 行,每行两个整数,分别是 $t_i$($0 \le t_i \le 1,000$)和 $p_i$($0 \le p_i \le 1,000$)。

输出格式

输出到标准输出。 输出共一行,表示老师狂暴程度 $r$ 的最小值。

样例 #1

样例输入 #1

3
5 1
1 4
2 2

样例输出 #1

2

算法

算法1

(二分答案) $O(n\log n)$

首先可以考虑二分答案,答案 $mid$ 表示老师狂暴程度的最大值,然后检验是否可行。

检验是否可行的时候可以用贪心,将同学按照答疑时间从小到大排序,然后依次加入队列,如果加入后队列中的最大暴躁程度 $maxg$ 大于 $mid$,则需要弹出队首的同学,直到 $maxg$ 不大于 $mid$,然后继续加入下一个同学。

时间复杂度:$O(n\log^2 n)$,其中一重 $\log$ 是二分答案,另外一重 $\log$ 是排序。

C++ 代码

#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 1000;

int n;
int t[MAXN], p[MAXN];

bool check(int mid) {
    int maxg = 0;
    int q[MAXN], head = 0, tail = 0;
    for (int i = 0; i < n; i++) {
        while (head < tail && t[q[head]] + t[i] - p[i] > mid) {
            head++;
        }
        q[tail++] = i;
        maxg = max(maxg, t[q[tail - 1]] - p[q[tail - 1]]);
        if (maxg > mid) {
            return false;
        }
    }
    return true;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> t[i] >> p[i];
    }
    int l = 0, r = 1000 * n;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (check(mid)) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << l << endl;
    return 0;
}

算法2

(单调队列) $O(n)$

上面的算法中,每次检验是否可行都需要排序,时间复杂度较高。可以考虑优化这一部分。

由于当加入一个新的同学时,需要弹出队首,因此可以想到用单调队列来维护队列中的最大暴躁程度。

具体来说,用一个单调递减的队列 $q$ 来存储同学的编号,队列中的同学按照答疑时间从小到大排列。依次加入同学 $i$,当队首的同学 $j$ 的答疑时间和 $i$ 的答疑时间之差大于 $mid$ 时,需要弹出 $j$,直到队首的同学 $k$ 的答疑时间和 $i$ 的答疑时间之差不大于 $mid$。在弹出同学 $j$ 后,队列中剩下的同学的暴躁程度会发生变化,但是只有新增加的同学 $i$ 可能会使得暴躁程度变大,因此只需要更新队首到同学 $i$ 之间的同学的暴躁程度即可。具体来说,设 $q$ 中的同学编号为 $j_1,j_2,\dots,j_k$,则在加入同学 $i$ 之前,$q$ 中的同学的暴躁程度 $g_{j_1},g_{j_2},\dots,g_{j_k}$ 满足 $g_{j_1} \ge g_{j_2} \ge \dots \ge g_{j_k}$。加入同学 $i$ 后,需要更新 $q$ 中的同学的暴躁程度,具体来说,对于 $j_1,j_2,\dots,j_k$ 中的每个同学 $j$,要将 $g_j$ 更新为 $g_j + t_j - p_i$。

时间复杂度:$O(n)$。

C++ 代码

#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 1000;

int n;
int t[MAXN], p[MAXN];

bool check(int mid) {
    int q[MAXN], head = 0, tail = 0;
    int maxg = 0;
    for (int i = 0; i < n; i++) {
        while (head < tail && t[q[head]] + t[i] - p[i] > mid) {
            head++;
        }
        q[tail++] = i;
        if (head < tail) {
            maxg = max(maxg, t[q[head]] - p[q[head]]);
        }
        for (int j = head; j < tail; j++) {
            t[q[j]] += t[i] - p[i];
        }
        if (maxg > mid) {
            return false;
        }
    }
    return true;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> t[i] >> p[i];
    }
    int l = 0, r = 1000 * n;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (check(mid)) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << l << endl;
    return 0;
}

原文地址: https://www.cveoy.top/t/topic/n3in 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录