题意
分析
非常神仙的倍增做法。
我们设 \(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';
}
}
标签:joisc2017,int,题解,rx,maxn,区间,Railway,nr,lx
From: https://www.cnblogs.com/redacted-area/p/18379540