[CSP-S 2024] 超速检测 逐个测试点详解,带你骗分
考场上如果想不到正解,不妨先写其中测试点的答案,一方面是先拿到一些分数,另一方面也可以脱开思路,有助于想到正确的解法。
题目大意
一条路从南到北长度 \(L\),若干车辆从特定位置 \(d_i\),特定速度 \(v_i\),进入道路,以恒定加速度 \(a_i\) 行驶,速度为 \(0\) 算驶离。道路上有若干测速仪。问总共有多少车辆超速,最少用几个测速仪就可以测全部超速车辆。
特殊性质A 20分
特殊性质A:保证 \(a_i=0\) 即所有车都以恒定速度行驶,那么所有车都会从道路尽头离开道路,不会中间退出。我们只需要选取最远的测速点,就可以保证最大限度的不漏超速车辆。但如果最远的测速点也没有测到超速车辆,那么就可以撤掉所有超速点。
const int MAXN = 100000+5;
int T;
int n,m,L,V;
int d[MAXN],v[MAXN],a[MAXN],p[MAXN];
void input(){
cin>>n>>m>>L>>V;
for(int i=0;i<n;i++){
cin>>d[i]>>v[i]>>a[i];
}
for(int i=0;i<m;i++){
cin>>p[i];
}
}
void solveA(){
int ans = 0;
if( m == 0){
cout<<"0 0\n";
return ;
}
// 找到最远的测速点
int pp = p[0];
for(int i=0;i<m;i++){
if(pp < p[i]){
pp = p[i];
}
}
for(int i=0;i<n;i++){
if(v[i] > V && d[i] <= pp){
ans ++;
}
}
if(ans > 0)
cout<<ans<<" "<<m-1<<endl;
else
cout<<ans<<" "<<m<<endl;
}
int main(){
freopen("detect3.in","r",stdin);
cin>>T;
while(T--){
input();
bool checkA = true;
for(int i=0;i<n;i++){
if(a[i] != 0)checkA = false;
}
if(checkA){
solveA();
}
}
return 0;
}
特殊性质B 20分
特殊性质B,保证 \(a_i\gt0\) 即所有车都加速行驶,这里的情况和A类似,所有车辆都只能从道路尽头驶离道路。假设一辆车被某个测速点测到超速,由于一直在加速,那一定会被最后一个测速点测到超速。我们只需要在A的基础上稍加修改,并分类讨论初始速度的情况。
\[超速开始位置\begin{cases}
d_i - esp &\text{if }v_i\gt V& 已经超速了,所以向前减一点 \\
d_i &\text{if }v_i=V& 刚好开始加速,从开始位置开始超速\\
d_i +(V^2-v_i^2)/2a_i+esp&\text{if }v_i\gt V & 经过一段路程后开始超速\\
\end{cases}
\]
\[(esp:取0.0000001,为了解决超速点速度假如恰好等于V但不算超速的情况)
\]
const double ESP = 0.0000001;
void solveB(){
int ans = 0;
if( m == 0){
cout<<"0 0\n";
return ;
}
// 找到最远的测速点
int pp = p[0];
for(int i=0;i<m;i++){
if(pp < p[i]){
pp = p[i];
}
}
for(int i=0;i<n;i++){
double start ;
if(v[i] > V) start = d[i] - ESP;
else if(v[i] == V) start = d[i];
else if(v[i] < V) start = d[i] + (V*V - v[i]*v[i])/(2.0*a[i]) + ESP;
if(start < pp){
ans++;
}
}
if(ans > 0)
cout<<ans<<" "<<m-1<<endl;
else
cout<<ans<<" "<<m<<endl;
}
特殊性质C 20分
这个没啥特殊的了,不如下面来看看小数据规模吧
\(n\le10\) 10分
最简单暴力的方法,就是首先预处理每辆车的超速区间,然后枚举每个测速点是否启用,再判断每辆车在哪些测速点超速了。
当 \(n\le10\) 时,方案总数\(2^n\approx10^3\),每一种方案需要枚举判断每辆车,复杂度 \(n\),一辆车的超速区间需要二分判断 \(logm\) ,总复杂度 \(T2^nnlogm \approx 10^6\)。
const int MAXN = 100000+5;
int T;
int n,m,L,V;
int d[MAXN],v[MAXN],a[MAXN],p[MAXN];
void input(){
cin>>n>>m>>L>>V;
for(int i=0;i<n;i++){
cin>>d[i]>>v[i]>>a[i];
}
for(int i=0;i<m;i++){
cin>>p[i];
}
}
int range[MAXN][2];
int rangeCnt;
void addRange(double start,double end){
int* l = upper_bound(p,p+m,start); // 大于
int* r = upper_bound(p,p+m,end); // 大于
if(r > l){
range[rangeCnt][0] = l-p;
range[rangeCnt][1] = r-p;
rangeCnt++;
}
}
const double ESP = 0.0000001;
void pre(){
for(int i=0;i<n;i++){
if(a[i] == 0){
if(v[i] > V){
addRange(d[i]-ESP,L);
}
}else if(a[i] > 0){
double start ;
if(v[i] > V) start = d[i] - ESP;
else if(v[i] == V) start = d[i];
else if(v[i] < V) start = d[i] + (V*V - v[i]*v[i])/(2.0*a[i]) + ESP;
addRange(start,L);
}else if(a[i] < 0 && v[i] > V){
double end = d[i]+(V*V - v[i]*v[i])/(2.0*a[i]) - ESP;
addRange(d[i]-ESP,end);
}
}
}
int plan[MAXN],planCnt;
int ans;
int lastFailI;
bool check(){
for(int i=0;i<rangeCnt;i++){
// 车辆的超速区间是[range[i][0],range[i][1]) plan是启用了的测速点
// 需要判断plan里面的点有没有在车辆的超速区间内
int* l = lower_bound(plan,plan+planCnt,range[i][0]); // l 是一个大于等于range[i][0]的
int* r = lower_bound(plan,plan+planCnt,range[i][1]); // r >= range[i][1]
if(r <= l){
return false;
}
}
return true;
}
void dfs(int v){
if(v == m){
// 判断方案是否合法
if(check()){
if(planCnt < ans){
ans = planCnt;
}
}
return;
}
dfs(v+1);
plan[planCnt++] = v;
dfs(v+1);
planCnt--;
}
void solveS(){
ans = m;
planCnt = 0;
rangeCnt = 0;
// 分两步,首先预处理所有车辆的超速区间
pre();
// 其次枚举所有方案
dfs(0);
printf("%d %d\n",rangeCnt,m-ans);
}
int main(){
cin>>T;
while(T--){
input();
if(n <= 20)
solveS();
}
return 0;
}
\(n\le20\) 10分
如果我们再采取上面的方法,\(n=20\)时,复杂度可以达到\(10^9\)。常数有一点点大,我们需要引入这样两个神奇的剪枝优化:
- 二分答案,如果保留10个测速点是答案,那么11个测速点肯定也能测到所有超速车辆,9个测速点一定会漏测。
- 由于枚举方案的时候,相邻方案之间十分相似,那么也大概率会因为没有测到同一辆超速的车而被淘汰,我们可以优先用上次方案当中没有测到的车辆来考验下次的方案。
实测: 在样例2当中,通过以上两个优化,可以减少90%的判断。
const int MAXN = 100000+5;
int T;
int n,m,L,V;
int d[MAXN],v[MAXN],a[MAXN],p[MAXN];
void input(){
cin>>n>>m>>L>>V;
for(int i=0;i<n;i++){
cin>>d[i]>>v[i]>>a[i];
}
for(int i=0;i<m;i++){
cin>>p[i];
}
}
const double ESP = 0.0000001;
int range[MAXN][2];
int rangeCnt;
void addRange(double start,double end){
int* l = upper_bound(p,p+m,start); // 大于
int* r = upper_bound(p,p+m,end); // 大于
if(r > l){
range[rangeCnt][0] = l-p;
range[rangeCnt][1] = r-p;
rangeCnt++;
}
}
void pre(){
for(int i=0;i<n;i++){
if(a[i] == 0){
if(v[i] > V){
addRange(d[i]-ESP,L);
}
}else if(a[i] > 0){
double start ;
if(v[i] > V) start = d[i] - ESP;
else if(v[i] == V) start = d[i];
else if(v[i] < V) start = d[i] + (V*V - v[i]*v[i])/(2.0*a[i]) + ESP;
addRange(start,L);
}else if(a[i] < 0 && v[i] > V){
double end = d[i]+(V*V - v[i]*v[i])/(2.0*a[i]) - ESP;
addRange(d[i]-ESP,end);
}
}
}
int plan[MAXN],planCnt;
int ans;
int lastFailI; // 上次失败的序号
bool check(){
for(int j=0;j<rangeCnt;j++){
int i = (j + lastFailI)%rangeCnt; // 从上次失败的序号开始
// 车辆的超速区间是[range[i][0],range[i][1]) plan是启用了的测速点
// 需要判断plan里面的点有没有在车辆的超速区间内
int* l = lower_bound(plan,plan+planCnt,range[i][0]); // l >= range[i][0]
int* r = lower_bound(plan,plan+planCnt,range[i][1]); // r >= range[i][1]
if(r <= l){
lastFailI = i;
return false;
}
}
return true;
}
void dfs(int v,int maxPlanCnt){
if(v==m){
// 判断方案是否合法
if(check()){
if(planCnt < ans){
ans = planCnt;
}
}
return;
}
if(maxPlanCnt - planCnt < m-v ){
dfs(v+1,maxPlanCnt);
}
if(maxPlanCnt > planCnt){
plan[planCnt++] = v;
dfs(v+1,maxPlanCnt);
planCnt--;
}
}
void solveS(){
ans = m;
planCnt = 0;
rangeCnt = 0;
// 分两步,首先预处理所有车辆的超速区间
pre();
// 其次枚举所有方案
int L=-1,R=m;
while(true){
if(L+1 == R)break;
int M = (L+R)/2;
dfs(0,M);
if(ans == M){
R = M;
}else{
L = M;
}
}
printf("%d %d\n",rangeCnt,m-ans);
}
int main(){
cin>>T;
while(T--){
input();
if(n <= 20)
solveS();
}
return 0;
}
最终正解
- 首先计算每辆车的超速区间。
- 然后按区间左端点为第一关键字,右端点为第二关键字进行排序。
- 然后删除多余的无用区间,如果区间a在区间b的内部,也就是la <= lb,ra>=rb,那么区间a就是对答案没有贡献的,因为能测到b超速的测速仪一定会测到a超速。删除多余区间之后,可以保证右端点也是升序排列了。
- 在排好序的区间内,选第一个区间,取右端点测速仪。这个测速仪就是需要保留的,判断所有超速的区间,并从列表当中剔除,然后重复此步骤。
优化:实际写代码的时候,我们可以利用右端点必须升序排列这一特点来筛选多余区间。在第二步骤的排序之后,我们可以倒序遍历列表,找到右端点突出的删掉即可。
1: ---
2: -----
3: ------ <-这个就是需要删除的,因为右端点比4号的大
4: ---
5: -----
6: ------
const int MAXN = 100000+5;
int T;
int n,m,L,V;
int d[MAXN],v[MAXN],a[MAXN],p[MAXN];
void input(){
cin>>n>>m>>L>>V;
for(int i=0;i<n;i++){
cin>>d[i]>>v[i]>>a[i];
}
for(int i=0;i<m;i++){
cin>>p[i];
}
}
const double ESP = 0.0000001;
int range[MAXN][2];
int rangeCnt;
void addRange(double start,double end){
int* l = upper_bound(p,p+m,start); // 大于
int* r = upper_bound(p,p+m,end); // 大于
if(r > l){
range[rangeCnt][0] = l-p;
range[rangeCnt][1] = r-p;
rangeCnt++;
}
}
void pre(){
for(int i=0;i<n;i++){
if(a[i] == 0){
if(v[i] > V){
addRange(d[i]-ESP,L);
}
}else if(a[i] > 0){
double start ;
if(v[i] > V) start = d[i] - ESP;
else if(v[i] == V) start = d[i];
else if(v[i] < V) start = d[i] + (V*V - v[i]*v[i])/(2.0*a[i]) + ESP;
addRange(start,L);
}else if(a[i] < 0 && v[i] > V){
double end = d[i]+(V*V - v[i]*v[i])/(2.0*a[i]) - ESP;
addRange(d[i]-ESP,end);
}
}
}
int id[MAXN],id2[MAXN],removed[MAXN];
int com(int a,int b){
if(range[b][0] != range[a][0]){
return range[a][0] < range[b][0];
}
return range[a][1] > range[b][1];
}
void solve(){
for(int i=0;i<n;i++){
id[i] = i;
}
rangeCnt = 0;
sort(p,p+m);
// 预处理所有车辆的超速区间
pre();
for(int i=0;i<rangeCnt;i++){
removed[i] = false;
}
//按区间左端点为第一关键字,右端点为第二关键字进行排序。
sort(id,id+rangeCnt,com);
int minR = range[id[rangeCnt-1]][1];
for(int i=rangeCnt-1;i>=0;i--){
if(range[id[i]][1] > minR){
removed[i] = true;
}else{
minR = range[id[i]][1];
}
}
int ans = 0;
for(int i=0;i<rangeCnt;i++){
if(!removed[i]){
int cnt = range[id[i]][1]-1;
ans++;
for(;i<rangeCnt;i++){
if(range[id[i]][0] <= cnt){
removed[i] = true;
}else{
i--;
break;
}
}
}
}
cout<<rangeCnt<<" "<<m-ans<<endl;
}
int main(){
cin>>T;
while(T--){
input();
solve();
}
return 0;
}

浙公网安备 33010602011771号