P9166 [省选联考 2023] 火车站
这道题很抽象,有这么几点注意事项
1,火车必须走到尽头才可以停下,所以答案一定会出于输入的这些端点
2,火车只能往一个方向走,不可以在中途换向
那么这题怎么处理?不会真的要一波操作然后把所有答案排个序吧?
我选择标记法!标记答案,省去了排序的过程。
那么哪些端点是答案?
这是个大问题!
那么我们开始构思,头脑风暴法?瞪眼法!考场三个小时直接不断优化,不断更新思路
从一开始的想要建图,想要掏出杀手锏search,到后来冷静分析,用有限的数据找到题目的精髓,找到答案统计的本质,经过一波分析,我决定从新出发,用最简朴的方式,最朴素的算法切掉这题,数据规模不大,才2e5,那么我们可以直接相信奥义,既然题干告诉你这是火车站,那么我们化身为工程师了,总览全局,真正融入修铁路这门事业
考虑到可能数据规模较大,我们就要边输入边扩建,从起点扩建范围,只有与已建好铁路范围有交集的铁路才可以实现已建好铁路的扩建,那么输入的过程顺便修点铁路也可以神奇的省去复杂度,早开工早下班
那么假设我们已经拿到了一段修好的铁路,什么是答案?
分为以下几种情况
1,这条轨道区间在已经建好的范围内
x在这条轨道区间内 左右端点都是答案
x在这条轨道区间左边 右端点是答案
x在这条轨道区间右边 左端点是答案
2,这条轨道左端点在起点和已经建好的右端点之间,右端点在外边
右端点是答案
3,这条轨道右端点在已经建好的左端点和起点之间,左端点在外边
左端点是答案
4,已经建好的在这条轨道区间的范围内
这一轮没用,先丢进队列
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
struct pp {
int a, b;
} e[200001];
queue<int> q;
int now_l = 200001, now_r, cnt, w, copy_m/*剩余轨道数量*/;
bool ans[200001], p[200001];
int main() {
int n, m, x;
scanf("%d%d%d", &n, &m, &x);
copy_m = m;
for(int i = 1, l, r; i <= m; ++i) {
scanf("%d%d", &l, &r);//输入轨道边界
e[++cnt].a = l;//存轨道
e[cnt].b = r;
q.push(cnt);//以轨道编号压入队列
if(l <= x && r >= x) now_l = min(l, now_l), now_r = max(r, now_r);//扩建
}
int now;
while(!q.empty()) {
now = q.front();
q.pop();
if(e[now].a >= now_l && e[now].b <= now_r) {//这条轨道在建好的轨道之间
if(e[now].a <= x && e[now].b >= x)//起点在这条轨道上
ans[e[now].a] = 1, ans[e[now].b] = 1;//这条轨道两端都可以到
else if(e[now].a >= x && e[now].b >= x) {//整个轨道在起点右边
ans[e[now].b] = 1;//这条轨道只有右端点可以到
} else if(e[now].a <= x && e[now].b <= x) {//整个轨道在起点左边
ans[e[now].a] = 1;//这条轨道只有左端点可以到
}
copy_m--;
w = 0;
continue;
} else if(e[now].a >= x && e[now].a <= now_r && e[now].b > now_r) {//这条轨道左端点在已经建好的轨道范围内,右端点出界
now_r = e[now].b;//扩建
ans[e[now].b] = 1;//右端点可以到
copy_m--;
w = 0;
continue;
} else if(e[now].a < now_l && e[now].b >= now_l && e[now].b <= x) {//这条轨道右端点在已经建好的轨道范围内,左端点出界
now_l = e[now].a;//扩建
ans[e[now].a] = 1;//左端点可以到
copy_m--;
w = 0;
continue;
} else {//与已经建好的轨道无交集
q.push(now);//再次压入队列
w++;
if(w == copy_m) break;//如果连续压入队列的次数等于剩余轨道数量,说明剩下的轨道是飞舞,退出循环
}
}
for(int i = 1; i <= n; i++) {
if(i == x) continue;
if(ans[i]) printf("%d ", i);//被标记过的点可以到达
}
return 0;
}
上面的思路貌似无懈可击,这还不够,我们依旧不满足
一个叫做(名字)的人曾说过,人们最初的想法是冗杂的。
嗯对,他确实说过
其实上面的思路已经很明确了,我们浅浅的总结一下,可以省去不少麻烦
1,这条轨道左端点在起点和修好的铁路右端点之间,说明可以换道,
右端点是答案,新的右端点保留最大值
2,这条轨道右端点在修好的铁路左端点和起点之间,说明可以换道,
左端点是答案,新的左端点保留最小值
3,这条轨道左端点在起点左边,右端点在x右边
左右端点都是答案,新的右端点保留最大值,同时新的左端点保留最小值
4,已经建好的在这条轨道区间的范围内
这一轮没用,先丢进队列
不错,起码码量分情况简便了许多
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
struct pp {
int a, b;
} e[200001];
queue<int> q;
int now_l = 200001, now_r, cnt, w, copy_m/*剩余轨道数量*/;
bool ans[200001], p[200001];
int main() {
int n, m, x;
scanf("%d%d%d", &n, &m, &x);
copy_m = m;
for(int i = 1, l, r; i <= m; ++i) {
scanf("%d%d", &l, &r);//输入轨道边界
e[++cnt].a = l;//存轨道
e[cnt].b = r;
q.push(cnt);//以轨道编号压入队列
if(l <= x && r >= x) now_l = min(l, now_l), now_r = max(r, now_r);//扩建
}
int now;
while(!q.empty()) {
now = q.front();
q.pop();
if(e[now].a >= x && e[now].a <= now_r) {
ans[e[now].b] = 1;
now_r = max(now_r , e[now].b);
copy_m --;
w = 0;
continue;
} else if(e[now].b <= x && e[now].b >= now_l) {
ans[e[now].a] = 1;
now_l = min(now_l , e[now].a);
copy_m --;
w = 0;
continue;
} else if(e[now].a <= x && e[now].b >= x) {
now_l = min(now_l , e[now].a);
now_r = max(now_r , e[now].b);
ans[e[now].a] = 1;
ans[e[now].b] = 1;
copy_m --;
w = 0;
continue;
} else {
q.push(now);
w++;
if(w == copy_m) break;
}
}
for(int i = 1; i <= n; i++) {
if(i == x) continue;
if(ans[i]) printf("%d ", i);//被标记过的点可以到达
}
return 0;
}