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;
}

posted @ 2022-06-06 20:45  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(80)  评论(0编辑  收藏  举报