ABC254G Elevators[题解]
Elevators
\(Problem\)
给定 \(N\) 栋楼,每栋楼有 \(10^9\) 层。
你可以花费 \(1\) 的时间从任意一栋楼的第 \(i\) 层到达其他楼的第 \(i\) 层。
有 \(M\) 个电梯,第 \(i\) 个电梯属于第 \(A_i\) 栋楼,覆盖范围为 \(B_i\) 到 \(C_i\),从第 \(A_i\) 栋楼的第 \(x\) 层到第 \(y\) 层的花费的时间为 \(|x -y|\),满足 \(1\leq B_i\leq x,y\leq C_i\leq 10^9\)。
现在给定 \(Q\) 个询问,问从第 \(i\) 栋楼的第 \(x\) 层能否到达第 \(j\) 栋楼的第 \(y\) 层,如不能,输出 \(-1\),否则输出最短时间。
\(1\leq N,M,Q\leq 2\times 10^6\)
\(Sol\)
不难想到的是,从第 \(x\) 层到第 \(y\) 层实际上固定的花费是 \(|x-y|\),问题的关键在于我从第 \(x\) 层到达第 \(y\) 层要转几次楼。至此,我们将问题划归为最少换楼的次数。
首先,对于 \(x == y\) 即楼层相同的情况下,答案只能是 \(0\) 或者 \(1\),这一点毋庸置疑。
对于 \(x \neq y\) ,我们可以将其视为同一种情况,即 \(x<y\),若不满足,可以通过交换起点与终点得到,下默认 \(x<y\)。
首先,对于每一栋楼,我们可以先将其本身进行合并,即两相交或相含的电梯可以看作一个电梯。
假设我们当前的位置已知,考虑下一步。首先,我们要向上爬,且楼层可以转化,一个比较自然的想法是每一步尽可能地走得远。思考其正确性,由于电梯可以在被其覆盖的任何一个区域进入,每次向上可以更新被其他电梯覆盖到的区域,向上越多更新到的越大,即走最远的电梯一定不亏。
故而我们可以将两个区段进行连边,由于每一个区段仅有一个向后的连边,故而我们可以将其看作一个森林,森林上的点互相可达,这时,询问即为森林中某棵树上的两个点。
至此,本题的大体思路基本已经清晰,但我们要如何实现,以及询问如何查询,是另一大难题。
首先,我们如何找到询问对应的节点?考虑将询问插入到给定的电梯中,在合并电梯形成的区段中,将询问视作一个节点。同时,我们没必要记录所有的区间信息,注意到除了最后以此,每次我们都是走到一个电梯的末尾,所以可以将电梯的端点作为图上的点存下来,询问就在这些点中,之后找到询问对应的点就有很多方式了。
查询采用倍增,从起点倍增到终点,记录深度,加上 \(|x-y|\) 即为答案。注意判断掉一些比较特殊的情况即可。
\(code\)
#include <bits/stdc++.h>
#define st first
#define nd second
#define mk make_pair
using namespace std;
const int N = 2e6 + 10, INF = 1e9;
inline int read()
{
int s = 0, cnt = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') cnt *= -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * cnt;
}
int n, m, Q, cnt;
int b[N], nex[N], dep[N], f[N][21];
unordered_map<int, int> id;
vector<pair<int, int> > p[N];
struct node{ int x, y, z, w; }que[N];
inline void update(vector<pair<int,int> > &v)
{
sort(v.begin(), v.end());
vector<pair<int,int> > tem;
int mxr = 0, lst = -1;
for(register pair<int, int> u : v){
int l = u.st, r = u.nd;
if(lst == -1) { lst=l, mxr=r; continue; }
if(l > mxr) tem.push_back(mk(lst, mxr)), lst = l;
mxr = max(mxr, r);
}
if(lst != -1) tem.push_back(mk(lst,mxr));
v = tem;
}
inline int getA(int x, int y) //找到 A 的位置
{
int pos = upper_bound(p[x].begin(), p[x].end(), mk(y, INF)) - p[x].begin() - 1;
if(pos == -1) return -1;
if(y >= p[x][pos].st && y <= p[x][pos].nd) return id[p[x][pos].nd]; //返回右端点的值
return -1;
}
inline int getB(int x,int y) //找到 B 的位置
{
int pos = upper_bound(p[x].begin(), p[x].end(), mk(y,INF)) - p[x].begin() - 1;
if(pos == -1) return -1;
if(y >= p[x][pos].st && y <= p[x][pos].nd) return id[p[x][pos].st];
return -1;
}
signed main()
{
n = read(), m = read(), Q = read();
for(register int i = 1; i <= m; i++){
int a = read(), l = read(), r = read();
p[a].push_back(mk(l, r));
}
for(register int i = 1; i <= Q; i++){
int x = read(), y = read(), z = read(), w = read();
que[i]=(node){x, y, z, w};
p[que[i].x].push_back(mk(y, y));
p[que[i].z].push_back(mk(w, w));
}
for(register int i = 1; i <= n; i++){
update(p[i]); //合并
for(register pair<int, int> u : p[i]) b[++cnt] = u.st, b[++cnt] = u.nd;
}
sort(b + 1, b + cnt + 1);
cnt = unique(b + 1, b + cnt + 1) - b - 1; //去重
for(register int i = 1; i <= cnt; i++) id[b[i]] = i;
for(register int i = 1; i <= n; i++){
for(register pair<int, int> u : p[i]){
int l = lower_bound(b + 1, b + cnt + 1, u.st) - b;
int r = lower_bound(b + 1, b + cnt + 1, u.nd) - b;
nex[l] = max(nex[l], r);
}
}
for(register int i = 1; i <= cnt; i++) nex[i] = max(nex[i - 1], nex[i]);
for(register int i = cnt; i >= 1; i--){
if(nex[i] <= i) f[i][0] = cnt + 1; //没有后继
else f[i][0] = nex[i];
dep[i] = dep[f[i][0]] + 1;
}
f[cnt + 1][0] = cnt + 1;
for(register int j = 1; j <= 20; j++) //倍增
for(register int i = 1; i <= cnt + 1; i++) f[i][j] = f[f[i][j - 1]][j - 1];
for(register int i = 1; i <= Q; i++){
int x = que[i].x, y = que[i].y, z = que[i].z, w = que[i].w, ans = abs(y - w);
if(x == z && y == w) { printf("0\n"); continue; }
if(y > w) swap(x, z), swap(y, w);
int A = getA(x, y), B = getB(z, w);
if(A == -1 || B == -1) { printf("-1\n"); continue; }
if(A >= B) { printf("%d\n", ans + (x != z)); continue; } //在同一段区间
int tem = A;
for(register int i = 20; i >= 0; i--) if(f[tem][i] < B) tem = f[tem][i];
tem = f[tem][0];
if(tem == cnt + 1) { printf("-1\n"); continue; }
ans += dep[A] - dep[tem] + 1;
printf("%d\n", ans);
}
return 0;
}