[JOISC 2017] 火车旅行 / Railway Trip 题解
[JOISC 2017] 火车旅行 / Railway Trip
挺好的题目。
首先发现似乎是要求一个最短路,但是有 \(O(n^2)\) 条边太多了。发现,每个点只用向左右第一个比他大的点连边权为 \(1\) 的边即可,其他的边显然都可以用这些边组合出来,这可以单调栈处理出来。
然后考虑怎么快速求两点间最短路,考虑倍增,设 \(L_{i,j}\) 表示 \(i\) 走 \(2^j\) 步后能到的最左端点,\(R_{i,j}\) 为最右端点,怎么转移呢,因为可达的一个区间范围内,从端点走肯定不劣于从区间内部出发,所以 \(L_{i,j} = \min(L_{L_{i,j-1},j-1},L_{R_{i,j-1},j-1})\),右边同理。
每次询问就让起点和终点相互逼近就好了。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
inline int read(){
int k=0,f=1,c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())k=k*10+(c^48);
return k*f;
}
inline void write(int x){
if(x<10)putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
int n,k,m;
const int N = 1e5+10,inf=1e9;
int a[N],L[N][23],R[N][23],st[N],top;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
n=read(),k=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int i=1;i<=n;i++){
while(top&&a[st[top]]<a[i])top--;
L[i][0]=st[top];
st[++top]=i;
}
top=0;
for(int i=n;i>=1;i--){
while(top&&a[st[top]]<a[i])top--;
R[i][0]=st[top];
st[++top]=i;
}
for(int i=1;i<=n;i++){
if(!L[i][0])L[i][0]=1;
if(!R[i][0])R[i][0]=n;
}
for(int j=1;j<=21;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]);
}
}
for(int i=1;i<=m;i++){
int a,b;
a=read(),b=read();
if(a==b){
write(0);putchar(endl);
continue;
}
if(a>b)swap(a,b);
pii l={a,a},r={b,b};
int ans=0;
for(int j=21;j>=0;j--){
int ll=min(L[l.fst][j],L[l.scd][j]);
int rr=max(R[l.fst][j],R[l.scd][j]);
if(rr<b){
ans+=(1ll<<j);
l={ll,rr};
}
}
for(int j=21;j>=0;j--){
int ll=min(L[r.fst][j],L[r.scd][j]);
int rr=max(R[r.fst][j],R[r.scd][j]);
if(ll>l.scd){
ans+=(1ll<<j);
r={ll,rr};
}
}
write(ans);putchar(endl);
}
return 0;
}

浙公网安备 33010602011771号