用速度平方把每辆车能被测出超速的测速仪转成区间,再按右端点贪心求最少保留测速仪。
OJ: luogu
题目 ID: P11232
难度:提高+/省选-
标签:贪心二分区间覆盖
创建: 2026-06-22 18:29
更新: 2026-10-07 12:15
形式化题目
一条长度为
路上有
求两个数:
- 全部测速仪都开启时,有多少辆车会被判定超速;
- 在不漏掉这些超速车的前提下,最多能关闭多少台测速仪。
暴力解法
思路
先对每辆车枚举所有测速仪,记录它会在哪些测速仪处超速;然后枚举保留哪些测速仪,检查是否每辆超速车都至少被测到一次,取保留台数最少的方案。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-01 22:42
* update_at: 2026-10-01 22:42
*/
// brute.cpp:小数据暴力解,逐车逐测速仪判断超速,再枚举测速仪子集求最少保留台数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 20; // 暴力只服务小数据,m 也不能大(要枚举 2^m 个子集)
int T;
int n, m;
ll L, V; // L 道路长度,V 限速
ll d[MAXN], v[MAXN], a[MAXN]; // 每辆车的驶入位置、初速度、加速度
ll p[MAXN]; // 测速仪位置
vector<int> cover[MAXN]; // cover[i]:能测出第 i 辆车超速的测速仪下标
// 判断第 id 辆车在位置 pos 处是否超速,比较速度平方即可。
bool is_speeding(ll id, ll pos) {
ll speed_square = v[id] * v[id] + 2 * a[id] * (pos - d[id]);
return speed_square > 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;
int cnt = cover[i].size();
for (int k = 0; k < cnt; 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;
}复杂度
判断部分
瓶颈
正解
思路
用速度平方比较,避开浮点
根据题目给的公式,车在位置
超速等价于瞬时速度严格大于
全程只用整数比较,既不用开方也不用浮点数,没有精度问题。
即使
每辆车的超速测速仪是一段连续区间
式子关于
| 加速度 | 速度变化 | 超速测速仪的形态 |
|---|---|---|
| 不变 | 要么全程超速,要么全程不超速 | |
| 越走越快 | 一个后缀 | |
| 越走越慢 | 一个前缀 |
再注意到车只在
用 lower_bound 找到第一台不早于
- 匀速:
则区间是 ,否则没有区间; - 加速:二分第一个超速的测速仪
,区间是 ; - 减速:若
处已不超速则没有区间,否则二分最后一个超速的 ,区间是 。
有区间的车就是会被判定超速的车,所以第一问就是非空区间的个数。
第二问:最少点覆盖区间
要关掉尽量多的测速仪,等于保留尽量少的测速仪,使得每个区间至少含一个保留点。这是经典的最少点覆盖区间问题。
贪心策略:把区间按右端点从小到大看,如果当前区间还没被覆盖,就选它的右端点。选右端点最靠右,最有机会把后面的区间一起盖住,可以证明这样是最优的。
右端点天然在 [1, m],不必排序
通常做法要对区间排序。但这里区间的右端点就是测速仪下标,本来就在
max_left[r] = 右端点为 r 的区间里最大的左端点然后
这样贪心部分是
样例推演
样例
| 车 | 超速测速仪下标 | 说明 | |
|---|---|---|---|
| 1 | 无 | 匀速 |
|
| 2 | 匀速 |
||
| 3 | 加速;位置 |
||
| 4 | 减速;位置 |
||
| 5 | 无 | 在位置 |
第一问答案是
第二问先按右端点分桶:
r = 3: 2 > 0 -> 选 3
r = 5: 5 > 3 -> 选 5最少保留
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-01 22:48
* update_at: 2026-10-01 22:48
*/
// main.cpp:把每辆车能被测出超速的测速仪压成区间,再按右端点贪心求最少保留测速仪。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 100005;
int T;
int n, m;
ll L, V; // L 道路长度,V 限速
ll d[MAXN], v[MAXN], a[MAXN]; // 每辆车的驶入位置、初速度、加速度
ll p[MAXN]; // 测速仪位置,保证严格递增
int speeding_cars; // 超速车数量,也就是产生了区间的车辆数
int max_left[MAXN]; // max_left[r]:右端点为 r 的区间里最大的左端点
// 判断从 start_pos 以 start_speed 驶入、加速度为 acc 的车在位置 pos 处是否超速。
// 直接比较速度平方 v^2 + 2*acc*(pos - start_pos) 与 V^2,避免开方和浮点误差。
bool is_speeding(ll start_pos, ll start_speed, ll acc, ll pos) {
ll speed_square = start_speed * start_speed + 2 * acc * (pos - start_pos);
return speed_square > V * V;
}
// 记录一个测速仪下标区间 [left, right]。
// 后面的贪心只关心“同一个右端点上最大的左端点”,所以只保留这个值,不落地存整张区间表。
void record_interval(int left, int right) {
if (max_left[right] < left) {
max_left[right] = left;
}
speeding_cars++;
}
// 求出这辆车会在哪些测速仪处超速,把对应的连续下标区间记下来。
void add_interval(ll start_pos, ll start_speed, ll acc) {
// 测速仪位置递增,先找第一台不早于驶入位置的测速仪。
// 测速仪都在道路内(p[j] <= L),所以不用再按 L 截断。
int start = lower_bound(p + 1, p + m + 1, start_pos) - p;
if (start > m) {
return;
}
if (acc == 0) {
// 匀速车:速度一直不变,要么全程超速,要么全程不超速。
if (start_speed > V) {
record_interval(start, m);
}
return;
}
if (acc > 0) {
// 加速车:速度随位置单调变大,超速测速仪构成一个后缀,二分第一个超速的。
int left = start, right = m;
int first = m + 1;
while (left <= right) {
int mid = (left + right) / 2;
if (is_speeding(start_pos, start_speed, acc, p[mid])) {
first = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
if (first <= m) {
record_interval(first, m);
}
return;
}
// 减速车:速度随位置单调变小,超速测速仪构成一个前缀。
// 先确认起点处就已超速,再二分最后一个超速的测速仪。
if (start_speed <= V || !is_speeding(start_pos, start_speed, acc, p[start])) {
return;
}
int left = start, right = m;
int last = start;
while (left <= right) {
int mid = (left + right) / 2;
if (is_speeding(start_pos, start_speed, acc, p[mid])) {
last = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
record_interval(start, last);
}
// 最少保留多少台测速仪,才能让每辆超速车至少被测到一次。
// 标准区间贪心是按右端点排序后选右端点;这里右端点本身就是 [1, m] 的测速仪下标,
// 按右端点分桶后直接从小到大扫,省掉排序。
int min_required_sensors() {
int selected = 0;
int last_pos = 0;
for (int r = 1; r <= m; r++) {
// 右端点为 r 的区间里只要有一个还没被覆盖,就必须选 r。
// 选 r 最靠右,最有机会把后面的区间一起盖住。
if (max_left[r] > last_pos) {
selected++;
last_pos = r;
}
}
return selected;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> n >> m >> L >> V;
speeding_cars = 0;
for (int i = 1; i <= m; i++) {
max_left[i] = 0;
}
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 required = min_required_sensors();
cout << speeding_cars << ' ' << m - required << '\n';
}
return 0;
}复杂度
- 时间复杂度:
。每辆车二分求区间 ,贪心线性扫测速仪。 - 空间复杂度:
。
总结
这题分两步压缩:
- 用
把物理过程变成整数比较,并利用一次函数的单调性把每辆车压成一段测速仪下标区间; - 把“最多关多少台”变成“最少点覆盖区间”,按右端点贪心。
实现上还有一个小技巧:区间右端点本来就在
本文的 main.cpp 已用枚举子集的 brute.cpp 对拍