用速度平方把每辆车能被检测到的位置转成测速仪区间,再用右端点贪心求最少保留测速仪。
OJ: luogu
题目 ID: P11232
难度:普及+/提高
标签:贪心二分区间覆盖
日期: 2026-06-22 18:29
题意
有一条长度为 L 的道路,限速为 V。第 i 辆车从位置 d_i 驶入,初速度为 v_i,加速度为 a_i。道路上有 m 个测速仪,位置为递增的 p_j。
如果一辆车经过某个开启的测速仪时,瞬时速度严格超过 V,它就会被判定超速。
需要输出:
- 所有测速仪都开启时,有多少辆车会被判定超速;
- 在不漏掉这些超速车的前提下,最多能关闭多少个测速仪。
思路
先看一个直接暴力:对每辆车枚举所有测速仪,判断是否超速;然后枚举保留哪些测速仪,找覆盖所有超速车的最小保留集合。
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20;
int T;
int n, m;
long long L, V;
long long d[MAXN], v[MAXN], a[MAXN], p[MAXN];
vector<int> cover[MAXN];
bool is_speeding(long long id, long long pos) {
long long value = v[id] * v[id] + 2LL * a[id] * (pos - d[id]);
return value > V * V;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> n >> m >> L >> V;
for (int i = 1; i <= n; i++) {
cin >> d[i] >> v[i] >> a[i];
cover[i].clear();
}
for (int j = 1; j <= m; j++) {
cin >> p[j];
}
int speeding_cars = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (p[j] < d[i]) {
continue;
}
if (is_speeding(i, p[j])) {
cover[i].push_back(j);
}
}
if (!cover[i].empty()) {
speeding_cars++;
}
}
int best_keep = m;
int total = 1 << m;
for (int mask = 0; mask < total; mask++) {
bool ok = true;
for (int i = 1; i <= n && ok; i++) {
if (cover[i].empty()) {
continue;
}
bool caught = false;
for (int k = 0; k < (int)cover[i].size(); k++) {
int sensor = cover[i][k] - 1;
if (mask & (1 << sensor)) {
caught = true;
break;
}
}
if (!caught) {
ok = false;
}
}
if (ok) {
int keep = 0;
for (int j = 0; j < m; j++) {
if (mask & (1 << j)) {
keep++;
}
}
best_keep = min(best_keep, keep);
}
}
cout << speeding_cars << ' ' << m - best_keep << '\n';
}
return 0;
}暴力的瓶颈在于
我们先处理第一件事:一辆车会在哪些测速仪处超速。
根据题目给出的公式,车辆在位置 x 的速度平方为:
v_i^2 + 2a_i(x - d_i)判断超速只需要比较:
v_i^2 + 2a_i(x - d_i) > V^2不用开方,也不用浮点数。
因为这个式子关于 x 是单调的:
a_i = 0:速度不变;a_i > 0:速度随位置变大而变大,超速测速仪是一个后缀;a_i < 0:速度随位置变大而变小,超速测速仪是一个前缀。
所以每辆会被检测到的车,都可以转成测速仪下标上的一个连续区间 [l,r]。
求区间时,先用 lower_bound 找到第一台位置不小于 d_i 的测速仪。然后分三类:
- 匀速车:若
v_i > V,区间是[start,m]; - 加速车:二分第一个超速测速仪,区间是
[first,m]; - 减速车:若起点处已经不超速,则无区间;否则二分最后一个超速测速仪,区间是
[start,last]。
第一问就是非空区间的数量。
第二问变成:给定若干闭区间,选择尽量少的测速仪下标,使每个区间至少包含一个被选下标。
这是标准区间贪心:把区间按右端点从小到大排序。扫描时,如果当前已经选择的最后一个点不在当前区间内,就选择当前区间的右端点。这样既覆盖当前区间,又尽量靠右,最有机会覆盖后面的区间。
设最少需要保留 required 个测速仪,那么最多能关闭:
m - required代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int T;
int n, m;
long long L, V;
long long d[MAXN], v[MAXN], a[MAXN];
long long p[MAXN];
vector<pair<int, int> > intervals;
bool cmp_interval(const pair<int, int> &x, const pair<int, int> &y) {
if (x.second != y.second) {
return x.second < y.second;
}
return x.first < y.first;
}
bool is_speeding(long long d, long long v, long long a, long long pos) {
long long value = v * v + 2LL * a * (pos - d);
return value > V * V;
}
void add_interval(long long d, long long v, long long a) {
int start = lower_bound(p + 1, p + m + 1, d) - p;
if (start > m) {
return;
}
if (a == 0) {
if (v > V) {
intervals.push_back(make_pair(start, m));
}
return;
}
if (a > 0) {
int left = start, right = m;
int first = m + 1;
while (left <= right) {
int mid = (left + right) / 2;
if (is_speeding(d, v, a, p[mid])) {
first = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
if (first <= m) {
intervals.push_back(make_pair(first, m));
}
return;
}
if (v <= V || !is_speeding(d, v, a, p[start])) {
return;
}
int left = start, right = m;
int last = start;
while (left <= right) {
int mid = (left + right) / 2;
if (is_speeding(d, v, a, p[mid])) {
last = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
intervals.push_back(make_pair(start, last));
}
int min_required_sensors() {
// 区间按右端点排序,贪心选择当前区间的右端点。
sort(intervals.begin(), intervals.end(), cmp_interval);
int selected = 0;
int last_pos = 0;
for (int i = 0; i < (int)intervals.size(); i++) {
if (last_pos < intervals[i].first) {
selected++;
last_pos = intervals[i].second;
}
}
return selected;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> n >> m >> L >> V;
intervals.clear();
for (int i = 1; i <= n; i++) {
cin >> d[i] >> v[i] >> a[i];
}
for (int i = 1; i <= m; i++) {
cin >> p[i];
}
for (int i = 1; i <= n; i++) {
add_interval(d[i], v[i], a[i]);
}
int speeding_cars = (int)intervals.size();
int required = min_required_sensors();
cout << speeding_cars << ' ' << m - required << '\n';
}
return 0;
}复杂度
每辆车用二分求区间,复杂度为
总时间复杂度为
总结
这题的第一步是把物理公式离散化到测速仪位置上:用速度平方和 V^2 比较,避免浮点误差。
第二步是把每辆被检测到的车压成一个区间。只要得到区间,关闭测速仪的问题就变成“最少点覆盖所有区间”,按右端点贪心即可。