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距离的酒店
    }
posted @ 2025-09-04 15:42  katago  阅读(11)  评论(0)    收藏  举报