P11232 [CSP-S 2024] 超速检测
难度:普及+/提高。
考点:二分、贪心。
题意:
题意较长,没有题目大意,否则你也大意。
-
主干道长度为 L L L,有 n n n 辆车,看做左端点为 0 0 0,第 i i i 辆距离左端点处 d i d_i di 处驶入,初速度为 v 0 i v_{0_{i}} v0i,加速度为 a i a_i ai,当瞬间速度为 0 0 0 / 到达右端点 L L L 时驶离主干道。
-
m m m 个测试仪,第 j j j 个测试仪位于右端点 P j P_j Pj 处,每个测试仪可以启动/关闭。某辆车如果在启动的测速仪上瞬间速度超过了限速 V V V,则说明超速了,注意起点和终点都有可能会有测速仪,(有可能驶入主干道就超速了)。
题目所求:
第一问:如果 全部测速仪启动, n n n 辆车中会有多少辆车超速了。
第二问:如果想关闭一部分测试仪,且 不漏掉记录超速的车辆情况下,最多可以关闭多少个测速仪。
贴心的准备了加速度相关的公式(虽然只有一个有用);
分析:
Subtask 1、2: n , m ≤ 20 n,m \le 20 n,m≤20。
暴力枚举,每个测速仪开和关的状态,共 2 m 2^m 2m 种选择。再用 m × n m \times n m×n 来检查答案,但 2 20 × n × m ≤ 4 e 8 2^{20} \times n \times m \le 4e8 220×n×m≤4e8。尝试将 n × m n \times m n×m 优化到 O ( n ) O(n) O(n)。
分类讨论:
其中 p j p_j pj 表示车 i i i 在主干道上「首个」测速仪的位置, p m p_m pm 表示主干道最后一个测速仪的位置。
- 匀速:最大速度 [ p j , p m ] [p_j, p_m] [pj,pm] 一样,从头到尾速度不变,所以判断区间内任意一个测速仪都可以。
- 加速:最大速度在测速仪 p m p_m pm 位置,由于是匀加速运动,则整段路中最后一个测速仪时速度值最大,如果没有超速,那么主干道上都没超速。
- 减速:最大速度在测速仪 p j p_j pj 位置,由于是匀减速运动,则整段路中首个遇到的测速仪速度值最大。
通过二分来加速查找 p j p_j pj 「首个」测速仪的位置。
int j = std::lower_bound( p.begin(), p.end(), d[i] ) - p.begin();
时间复杂度优化到 O ( n l o g m ) ∗ 2 m O(nlogm)*2^{m} O(nlogm)∗2m, 20 p t s 20pts 20pts 到手。
Subtask 3、4:性质 A A A, a i = 0 a_i=0 ai=0(匀速运动), 20 p t s 20 pts 20pts。
非常 e a s y easy easy,直接判断 p j ∼ p m p_j \sim p_m pj∼pm 其中一个测速仪即可,不妨选择 p m p_m pm,如果 p m ≥ d i p_m \ge d_i pm≥di && v i > V v_i > V vi>V。说明车辆 i i i 在整段路都超速了,答案就是 m − j + 1 m-j+1 m−j+1 测速仪。
正解( 100 p t s 100pts 100pts ):包含 性质 B 、 C B、C B、C。
对于上一个问题进行深入分析:什么样的测速仪能检测车 i i i 超速。
不妨设 f i f_i fi:表示车 i i i 进入主干道「首个」测速仪的下标。
- 匀速, [ f i , m ] [f_i,m] [fi,m],所有碰到的测速仪都可以检测到。
- 加速, [ f i , m ] [f_i,m] [fi,m] 的后缀,如上图,匀加速运动,相当于一整段主干道速度都在提升,有可能是加速一段之后「瞬时速度」大于 V V V 碰到首个摄像头 P k 1 P_{k1} Pk1,由于 P k 1 ≥ f i P_{k1} \ge f_i Pk1≥fi 故这一段肯定属于 [ f i , m ] [f_i,m] [fi,m] 的「后缀」,实际可以表示为 [ p k 1 , m ] [p_{k1},m] [pk1,m]。
- 减速, [ f i , m ] [f_i,m] [fi,m] 的前缀,如上图,与加速相反,有可能一开始已经在超速,经过一段时间的减速后「瞬时速度」小于 V V V 碰到首个摄像头 P k 2 Pk_2 Pk2,由于 P k 2 ≤ m P_{k2} \le m Pk2≤m 故这一段肯定属于 [ f i , m ] [f_i,m] [fi,m] 的「前缀」,实际可以表示为 [ f i , p k 2 ] [f_i,p_{k2}] [fi,pk2]。
可以发现 符合要求 的是 连续的一段区间
[
f
i
,
m
]
[f_i,m]
[fi,m],(符合要求):拍摄到超速测速仪,也好求**(二分)**求「前缀 / 后缀」。
{
前缀,最后看到减速后不超速的点
后缀,最后看到加速后超速的点
\left\{ \begin{array}{lc} 前缀,最后看到减速后不超速的点\\ 后缀,最后看到加速后超速的点\\ \end{array} \right.
{前缀,最后看到减速后不超速的点后缀,最后看到加速后超速的点
二分的细节:check
第
i
i
i 车走
j
j
j 测速仪有无超速。
可以发现题目中给了几个公式,但实际上有用的只有一个:瞬时速度: v 0 2 + 2 × a × s \sqrt{ v_0^2 + 2 \times a \times s } v02+2×a×s。
二分就是 check
每辆车
i
i
i 在(初始速度为
V
0
V_0
V0,距离起点为
d
i
d_i
di,加速度为
a
i
a_i
ai)的瞬时速度:
V
0
2
+
2
×
a
×
(
p
j
−
d
i
)
>
V
\sqrt{ V_0^2 + 2 \times a \times( p_j-d_i ) } > V
V02+2×a×(pj−di)>V 。
根号会出现误差,最直接的办法就是想办法出掉精度丢失上带来的误差,直接两边平方, V 0 2 + 2 × a × ( p j − d i ) > V 2 V_0^2 + 2 \times a \times (p_j-d_i) > V^2 V02+2×a×(pj−di)>V2。
还要计算一下答案是否会超出 int
,
1
0
6
+
2
∗
1
0
9
≤
2147483647
(
i
n
t
)
10^6 + 2*10^9 \le 2147483647(int)
106+2∗109≤2147483647(int)。
(如上图所示,答案就是一段一段连续的区间,代表某辆车被检测到超速的测速仪区间)
问 1 1 1:区间的个数(超速车的数量)。
问 2 2 2:保留若干个点,便得每个区间至少有一个点。
第二个问题就很眼熟是吧(入门贪心:区间选点问题?),我们可以用(贪心) → \rightarrow → (区间的右端点排序),如下图。
作为
OI
教练一般不刻意压行,应该代码分块展示,会让 读者/学生 思路更加清晰。
参考代码:
#include <bits/stdc++.h>
#define ll long long
const int N = 1e5 + 10;
int n, m, L, V, d[N], v[N], a[N];
std::vector<int> p; // 寄存测速仪
struct Seg
{
int l, r; // 记录车 i 会被测速仪,检测到超速的区间 [l,r]
bool operator<(const Seg &a) // 贪心 ::: 右端点排序
{
return r < a.r;
}
};
std::vector<Seg> seg;
// 计算瞬间速度
ll speed(ll v, ll a, ll s)
{
return v * v + 2 * a * s; // 题目已经给出式子
}
void handler(int d, int v, int a)
{
// first 表示进入主干道后第一个碰到的测速仪(下标)
int first = std::lower_bound(p.begin(), p.end(), d) - p.begin();
// 进入主干道 ::: 没有遇到测速仪,跳过
if (first >= m)
return;
// 匀速 ::: 那一开始不超速意味着永远不会超速
if (a == 0)
{
if (v > V)
seg.push_back({first, m - 1});
return;
}
// 分类二分
int l = first, r = m - 1, ans = -1, mid;
// 减速,找到「最后一个」检测到超速的测速仪
if (a < 0)
{
while (l <= r)
{
mid = l + r >> 1;
if (speed(v, a, p[mid] - d) > V * V)
ans = mid, l = mid + 1;
else
r = mid - 1;
}
if (ans != -1)
seg.push_back({first, ans});
return;
}
// 加速,找到「首个」能检测到加速的测速仪
if (a > 0)
{
while (l <= r)
{
mid = l + r >> 1;
if (speed(v, a, p[mid] - d) > V * V)
ans = mid, r = mid - 1;
else
l = mid + 1;
}
if (ans != -1)
seg.push_back({ans, m - 1});
}
}
void solve()
{
p.clear(); // T 组数组记得初始化
seg.clear();
std::cin >> n >> m >> L >> V;
for (int i = 1; i <= n; i++)
std::cin >> d[i] >> v[i] >> a[i]; // 起点, 初始速度, 加速度
for (int i = 1, x; i <= m; i++)
std::cin >> x, p.push_back(x); // pi 测试仪的位置
for (int i = 1; i <= n; i++) // 处理出 ::: 每辆车 i 的测速仪超速的区间
handler(d[i], v[i], a[i]);
// 贪心 ::: 区间覆盖问题啦!
sort(seg.begin(), seg.end());
int last = -1, ans = 0;
for (auto it : seg)
if (last < p[it.l]) // 贪心的每次 ::: 选择不重合区间中的右端点
last = p[it.r], ans++;
std::cout << (int)seg.size() << " " << m - ans << "\n";
}
int main()
{
std::ios::sync_with_stdio(false), std::cin.tie(nullptr);
int T;
std::cin >> T;
while (T--)
solve();
return 0;
}