题解:洛谷 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
【核心思想】
- 问题分析:有 \(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\)。
-
算法选择:
- 差分 + 前缀和:由于 \(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\) 比较。
-
关键步骤:
- 读入:\(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。
-
时间/空间复杂度:
- 时间复杂度:\(O(N + Q + \max(c_i))\),预处理 \(O(N + MAX)\),查询 \(O(1)\)。
- 空间复杂度:\(O(MAX)\),其中 \(MAX = 10^6 + 5\)。
-
差分 + 前缀和的核心思想:
- 阈值转化:将每个农场的访问条件转化为起床时间 \(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
浙公网安备 33010602011771号