题解:洛谷 P10189 Maximizing Productivity

【题目来源】

洛谷:P10189 [USACO24FEB] Maximizing Productivity B - 洛谷

【题目描述】

Farmer John 有 \(N\)\(1\le N\le 2\cdot 10^5\))个农场,编号为 \(1\)\(N\)。已知 FJ 会在时刻 \(c_i\) 关闭农场 \(i\)。Bessie 在时刻 \(S\) 起床,她希望在农场关闭前访问尽可能多的农场,从而最大限度地提高她这一天的生产力。她计划在时刻 \(t_i+S\) 访问农场 \(i\)。Bessie 必须于严格早于 Farmer John 关闭农场的时刻抵达农场才能成功进行访问。

Bessie 有 \(Q\)\(1\le Q\le 2\cdot 10^5\))个询问。对于每个询问,她会给你两个整数 \(S\)\(V\)。对于每个询问,输出当 Bessie 在时刻 \(S\) 起床是否可以访问至少 \(V\) 个农场。

【输入】

输入的第一行包含 \(N\)\(Q\)

第二行包含 \(c_1,c_2,c_3\ldots c_N\)\(1\le c_i\le 10^6\))。

第三行包含 \(t_1,t_2,t_3\ldots t_N\)\(1\le t_i\le 10^6\))。

以下 \(Q\) 行,每行包含两个整数 \(V\)\(1\le V\le N\))和 \(S\)\(1\le S\le 10^6\))。

【输出】

\(Q\) 个询问的每一个输出一行,输出 YES(是)或 NO(否)。

【输入样例】

5 5
3 5 7 9 12
4 2 3 3 8
1 5
1 6
3 3
4 2
5 1

【核心思想】

  1. 问题分析:有 \(N\) 个农场,农场 \(i\) 在时刻 \(c_i\) 关闭。Bessie 在时刻 \(S\) 起床,访问农场 \(i\) 需要耗时 \(t_i\)(即她会在时刻 \(S + t_i\) 到达)。她必须在农场关闭之前(严格早于 \(c_i\))到达才能成功访问。对每个询问 \((S, V)\),判断 Bessie 能否访问至少 \(V\) 个农场。

关键转化:对于每个农场 \(i\),Bessie 能访问它当且仅当 \(S + t_i < c_i\),即 \(S < c_i - t_i\)。因此,定义每个农场的“最晚起床时间” \(limit_i = c_i - t_i - 1\)(即 \(S\) 的最大允许值)。若 Bessie 的起床时间 \(S \le limit_i\),则该农场可访问。问题转化为:给定 \(N\) 个阈值 \(limit_i\),对每个询问 \(S\),统计满足 \(limit_i \ge S\) 的农场数量,判断是否 \(\ge V\)

  1. 算法选择

    • 差分 + 前缀和:由于 \(c_i, t_i, S\) 的范围已知(\(\le 10^6\)),可以用数组 \(tot[x]\) 表示在起床时间 \(x\) 时能访问的农场数量。对于每个农场 \(i\),它在所有 \(S \le limit_i\) 时都可访问。因此,在 \(limit_i + 1\) 处做一个“减 1”标记,然后从 \(0\) 开始做前缀和(初始 \(tot[0] = N\),每遇到一个 \(limit_i + 1\) 就减 1),即可得到每个 \(S\) 对应的可访问农场数。最后对每个询问直接查询 \(tot[S]\) 并与 \(V\) 比较。
  2. 关键步骤

    • 读入\(N, Q\),关闭时间数组 \(c[1..N]\),旅行时间数组 \(t[1..N]\)
    • 初始化差分数组diff[0..MAX] 全 0,其中 `MAX = 1000005$。
    • 处理每个农场
      • 计算最晚可行起床时间:limit = c[i] - t[i](注意严格不等式,\(S\) 需满足 \(S < c_i - t_i\),即 \(S \le c_i - t_i - 1\))。
      • limit <= 0,则无论 Bessie 何时起床(\(S \ge 1\)),都无法访问(因为 \(S + t_i \ge 1 + t_i\),而 \(c_i \le t_i\),不满足严格小于)。代码中 limit 被限制为 max(limit, 1),并在 tot[limit] 处减 1。实际上,对于 \(S \ge 1\),若 \(limit \le 0\),则所有 \(S\) 都不满足,应将减 1 标记在位置 1(从 S=1 开始就不可达)。但代码使用 max(c[i]-t[i], 1),并在 tot 上做减 1,再通过前缀和计算。
    • 前缀和计算tot[0] = n;从 \(i=1\)\(MAX\)tot[i] += tot[i-1]
    • 处理询问:对于每个 \((S, V)\),若 tot[S] >= V,输出 YES,否则 NO
  3. 时间/空间复杂度

    • 时间复杂度:\(O(N + Q + \max(c_i))\),预处理 \(O(N + MAX)\),查询 \(O(1)\)
    • 空间复杂度:\(O(MAX)\),其中 \(MAX = 10^6 + 5\)
  4. 差分 + 前缀和的核心思想

    • 阈值转化:将每个农场的访问条件转化为起床时间 \(S\) 的上界约束。
    • 批量统计:利用差分数组,在 \(limit_i\) 处标记“从该起床时间起,可访问农场数减 1”,前缀和即可得到任意 \(S\) 的可访问农场数。
    • 整数边界处理:注意严格不等号 \(S + t_i < c_i\),即 \(S \le c_i - t_i - 1\)。代码中在 \(limit = c_i - t_i\) 处减 1,并令 tot[0] = n,经过前缀和后,tot[S] 表示的正是 \(S\) 时刻可访问的数量。
    • 适用场景:适用于大量询问、每个物品有一个“失效时间点”,需要快速查询在给定时间点有多少物品“有效”的问题。

【输出样例】

YES
NO
YES
YES
NO

【解题思路】

【算法标签】

普及- #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int n;                      // 农场数量
int q;                      // 查询次数
int c[200005];              // 存储每个农场的关闭时间
int t[200005];              // 存储从贝茜家到每个农场的旅行时间
int v;                      // 查询中需要访问的农场数量
int s;                      // 查询中贝茜的起床时间
int tot[1000005];           // 前缀和数组,tot[i]表示在时间i起床能访问的农场总数

int main()
{
    // 输入农场数量和查询次数
    cin >> n >> q;

    // 输入每个农场的关闭时间
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
    }

    // 输入从贝茜家到每个农场的旅行时间
    for (int i = 1; i <= n; i++)
    {
        cin >> t[i];

        // 计算该农场的最晚起床时间:max(c[i] - t[i], 1)
        // 如果在这个时间点之后起床,就无法访问该农场
        // 在对应位置标记减少一个可访问农场
        tot[max(c[i] - t[i], 1)]--;
    }

    // 初始化:在时间0起床可以访问所有n个农场
    tot[0] = n;

    // 计算前缀和:tot[i]表示在时间i起床能访问的农场数量
    for (int i = 1; i <= 1000005; i++)
    {
        tot[i] += tot[i - 1];
    }

    // 处理每个查询
    for (int i = 1; i <= q; i++)
    {
        // 输入需要访问的农场数量和贝茜的起床时间
        cin >> v >> s;

        // 判断在时间s起床是否能访问至少v个农场
        if (tot[s] >= v)
        {
            cout << "YES" << endl;
        }
        else
        {
            cout << "NO" << endl;
        }
    }

    return 0;
}

【运行结果】

5 5
3 5 7 9 12
4 2 3 3 8
1 5
YES
1 6
NO
3 3
YES
4 2
YES
5 1
NO
posted @ 2026-08-28 15:53  团爸讲算法  阅读(7)  评论(0)    收藏  举报