W
e
l
c
o
m
e
: )

题解:AT_joisc2017_f 鉄道旅行 (Railway Trip)

题意

鉄道旅行 (Railway Trip)

分析

非常神仙的倍增做法。

我们设 \(l_{i,j}\) 表示从 \(i\) 点出发,停靠 \(2^j\) 站后能抵达的最左位置。

同理设 \(r_{i,j}\) 表示从 \(i\) 点出发,停靠 \(2^j\) 站后能抵达的最右位置。

考虑如何更新这两个状态。

因为可以走回头路,所以简单的 \(l_{i,j}=l_{l_{i, j-1}, j-1}\)\(r_{i,j}=r_{r_{i, j-1}, j-1}\) 这种倍增方法是不行的。

我们可以考虑从两个端点来更新。

递推式如下:

\[\begin{aligned} l_{i,j}&=\min(l_{l_{i, j-1}, j-1}, l_{r_{i, j-1}, j-1})\\ r_{i,j}&=\max(r_{l_{i, j-1}, j-1}, r_{r_{i, j-1}, j-1}) \end{aligned} \]

每次查询时先从较左点向两侧扩展区间,如果该区间没有包含较右点,那么就扩展区间并统计答案。

然后再从较右点向两侧扩展区间,如果两区间没有交,那么扩展区间并统计答案。

最后得到的两区间再扩展一步就一定会有交。

Code

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005

int lis[maxn], l[maxn][18], r[maxn][18], stk[maxn], *top=stk;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int n, k, q;
    cin>>n>>k>>q;
    for(int i=1;i<=n;i++) cin>>lis[i];
    for(int i=1;i<=n;i++)
    {
        while(top!=stk&&lis[*top]<lis[i]) top--;
        if(top!=stk) l[i][0]=*top;
        else l[i][0]=i;
        *++top=i;
    }
    top=stk;
    for(int i=n;i;i--)
    {
        while(top!=stk&&lis[*top]<lis[i]) top--;
        if(top!=stk) r[i][0]=*top;
        else r[i][0]=i;
        *++top=i;
    }
    for(int j=1;j<18;j++)
        for(int i=1;i<=n;i++)
            l[i][j]=min(l[l[i][j-1]][j-1], l[r[i][j-1]][j-1]),
            r[i][j]=max(r[l[i][j-1]][j-1], r[r[i][j-1]][j-1]);
    int a, b, ans;
    while(q--)
    {
        cin>>a>>b;
        ans=0;
        if(a>b) swap(a, b);
        int lx=a, rx=a;
        for(int i=17, nl, nr;~i;i--)
        {
            nl=min(l[lx][i], l[rx][i]);
            nr=max(r[lx][i], r[rx][i]);
            if(nr<b) lx=nl, rx=nr, ans+=1<<i;
        }
        a=rx;
        lx=rx=b;
        for(int i=17, nl, nr;~i;i--)
        {
            nl=min(l[lx][i], l[rx][i]);
            nr=max(r[lx][i], r[rx][i]);
            if(nl>a) lx=nl, rx=nr, ans+=1<<i;
        }
        cout<<ans<<'\n';
    }
}
posted @ 2024-08-25 20:59  Jimmy-LEEE  阅读(38)  评论(0)    收藏  举报