AT_arc060_c [ARC060E] 高橋君とホテル 题解
https://www.luogu.com.cn/problem/AT_arc060_c
题目大意:
在一条线上的多个点之间,求从一个点到另一个点的最小步数,每步的距离有上限,且每步结束时必须停在一个点上。
// 算法思路:
// 1. 输入酒店的位置和最大跳跃距离L。
// 2. 使用二分,预处理每个酒店的最远可达酒店。
// - 对于每个酒店i,计算出在不超过L的距离内,能跳跃到的最远酒店的位置,并将结果存储在倍增表st[i][0]中。
// 3. 通过倍增法递推构建倍增表st[i][j],其中st[i][j]表示从酒店i跳跃2^j次后的目标酒店。
// - st[i][j]由st[st[i][j-1]][j-1]递推得到,保证每次跳跃的范围是2的幂次。
// 4. 对每个查询,利用倍增法跳跃找到最小天数。
// - 从起点l开始,逐步跳跃至终点r,每次尽量跳跃2^j的范围,直到到达目标。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n, a[N], st[N][21];
int L, Q, l, r;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>L;
for(int i=1;i<=n;i++){
int x=a[i]+L;
int pos=upper_bound(a+i, a+n+1, x)-a;//二分,找出第一个大于 x 的数。 这个可用双指针优化
pos-=1;//减一后就是最后一个 <=x 的数
st[i][0]=pos;
}
// 注意i, j 循环顺序, j要先来
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
st[i][j]=st[st[i][j-1]][j-1];
// for(int i=1;i<=n;i++)
// for(int j=0;j<=20;j++)
// printf(j==20?"%d\n":"%d ", st[i][j]);
cin>>Q;
while(Q--){
cin>>l>>r; if(l>r) swap(l, r);
int ans=0;
for(int j=20;j>=0;j--){
if(st[l][j]<r){// 如果跳跃2^j次后的目标位置小于r
l=st[l][j];// 跳跃到该位置
// ans+=1<<j;
// 也可以写成
ans|=1<<j;// 将跳跃次数累加到答案中
}
}
cout<<ans+1<<"\n";//同LCA, st[l][0]才是最终答案,也就是l的下一个节点才是答案,所以ans也要加1
}
return 0;
}
双指针
双指针法 可以在一次遍历中计算出 st[i][0],时间复杂度为 \(O(N)\) ,避免了每次查询都进行二分查找。
代码实现:
// 使用双指针计算st[i][0]
int j = 1; // 初始化第二个指针,指向第一个酒店
for (int i = 1; i <= n; i++) {
// 当酒店j在距离i酒店的范围内(即a[j] - a[i] <= L),就继续向前移动指针j
while (j <= n && a[j] - a[i] <= L) {
j++;
}
st[i][0] = j - 1; // j-1就是第一个不超过L距离的酒店
}

浙公网安备 33010602011771号