题解「JOISC 2017 Day 2 火车旅行」
非常好的题目。
约定,下文称一次乘坐中停靠的站点数(不包括起点和终点)为花费。
我们发现为了取到最优解,一定不会向 \(L\) 值比当前 \(L\) 小的点走。这样,当花费为 \(c(c\in N)\) 时,能够到达的 \(L\) 值不小于当前 \(L\) 值的点就构成了一段区间。
从区间的角度思考,如果一个区间 \(S_1\) 能够扩展到另一个区间 \(S_2\)(此时必有 \(S_1\subset S_2\)),我们就将他们连一条边,边权为这一次扩展的花费,但是这样做状态过多。不妨换个方式,我们提取出图中边权为 \(1\) 的边,构成一棵生成树。每次询问 \(x\to y\) 的最小停靠次数就变为求树上两点 \([x,x]\to [y,y]\) 的路径长度(因为起点和终点都不算停靠站)。
求树上两点距离自然使用 \(\text{LCA}\),但是区间的状态数过多,我们不可能显性建树。进一步思考,发现若从同一个点 \(z\) 出发,花费为 \(c\) 得到的区间是 \(S_1\) ,花费为 \(c+1\) 的区间必然包含花费为 \(c\) 的区间,并且若两个区间 \([L_1,R_1],[L_2,R_2]\) 满足 \(R_1+1=L_2\)(隐含\([L_1,R_1]\bigcap[L_2,R_2]=\emptyset\) ),则 \([L_1,R_1]\) 与 \([L_2,R_2]\) 有共同的父亲 \([L_1,R_2]\)。这样就可以使用倍增做法了。
设 \(f_{i,j}\) 为站点 \(i\) 停靠 \(2^j\) 个点时能够到达的最左点,\(g_{i,j}\) 为站点 \(i\) 停靠 \(2^j\) 个点能够到达的最右点。\(j=0\) 时单调栈处理即可。而当 \(j>0\) 时,有:
到达最左点,不一定是从之前的最左点转移过来的,到达最右点,不一定是从之前的最右点转移过来的,因此转移时不能只考虑一种情况。
注意一些并不是很繁琐的边界处理就好了qwq
#include<cstdio>
typedef long long ll;
int a[100005],st[100005],g[100005][25],f[100005][25];
inline int read() {
	register int x=0,f=1;register char s=getchar();
	while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
	while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
	return x*f;
}
inline void swap(int &x,int &y) {int tmp=x;x=y;y=tmp;}
inline int min(const int &x,const int &y) {return x<y? x:y;}
inline int max(const int &x,const int &y) {return x>y? x:y;}
int main() {
	int n=read(),m=read(),Q=read(),top=0;
	for(register int i=1;i<=n;++i) a[i]=read();
	top=0; st[++top]=1;
	for(register int i=1;i<=n;++i) {
		while(top&&a[st[top]]<a[i]) --top;
		f[i][0]=st[top];
		st[++top]=i;
	}
	top=0; st[++top]=n;
	for(register int i=n;i>=1;--i) {
		while(top&&a[st[top]]<a[i]) --top;
		g[i][0]=st[top];
		st[++top]=i; 
	}
	for(register int j=1;j<=20;++j) {
		for(register int i=1;i<=n;++i) {
			f[i][j]=min(f[f[i][j-1]][j-1],f[g[i][j-1]][j-1]);
			g[i][j]=max(g[g[i][j-1]][j-1],g[f[i][j-1]][j-1]);
		}
	}
	for(register int i=1;i<=Q;++i) {
		int x=read(),y=read(),l,r;
		ll ans=0; if(x>y) swap(x,y);
		l=r=x;
		for(register int j=20;j>=0;--j) {
			int nxtL=min(f[l][j],f[r][j]),nxtR=max(g[l][j],g[r][j]);
			if(nxtR<y) {l=nxtL,r=nxtR;ans+=(1<<j);}
		}
		x=r; l=r=y;
		for(register int j=20;j>=0;--j) {
			int nxtL=min(f[l][j],f[r][j]),nxtR=max(g[l][j],g[r][j]);
			if(nxtL>x) {l=nxtL,r=nxtR;ans+=(1<<j);}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号