【PKUSC2018】星际穿越
倍增
题目大意:
给定 $n$ 个点,第 $i(i>1)$ 个点有 $l_i$,表示点 $i$ 与 点 $l_i\sim i-1$ 之间的所有点都有一条权值为 $1$ 的边。
现在有 $q$ 组询问,每组询问给定 $L,R,x$,表示询问从 $[L,R]$ 中等概率选择一个点,$x$ 走到这个点的最短路的期望大小。
题解:
这个期望就是求个和再除以 $R-L+1$。
我们对第 $i$ 个点,令 $mn_i=\min_{j=i}^n l_j$.
我们求 $x$ 到 $y$ 的最短路时($x>y$),每次(除了第一次和最后一次)肯定从当前的 $k$ 走到 $[l_k,k-1]$ 中 $l_p$ 最小的结点 $p$,这样可以令下一次能够走的越远。
最后一次当然直接走到 $y$,那么第一次呢?
有一种办法是,从 $x$ 向后走一格,然后再向前走,这样可能比 $x$ 向前走两格更远。
那么我们考虑 $mn_x$ 的意义,就是以 $x$ 作为出发点时,走两步能走到的最远的点。
那么不是出发点的时候呢?
令能一步走到 $mn_x$ 的那个点为 $a$,那么如果当前的 $x$ 不是出发点,就说明出发点在 $a$ 的更后面,则一定能保证,某一次最远能走到 $x$ 的同时,也一定能走到 $a$。那么再多一次,最远能走到的点,就是从 $a$ 走到 $mn_x$ 了。
所以说,如果 $x$ 不能一次走到 $y$,那么 $x$ 肯定是不断变为 $mn_x$,最后到达 $y$。
那么考虑倍增,令 $f[i][k]$ 表示点 $i$ 不是起点时走 $2^k$ 次能到达的最远的点,$f[i][0]=mn_i$。
令 $g[i][k]$ 表示 $i$ 走到 $f[i][k]\sim i-1$ 所有点的最短步数之和。
预处理的时候,可以通过 $k-1$ 的值递推出 $k$ 的值。
查询的时候,由于答案满足可减性,对后缀进行倍增查询即可。注意要先计算掉开始花一步就能走到的点的贡献。
时间复杂度 $O((n+m)\log n)$。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=300005;
int n,q,f[N][19],L[N],mn[N],d[N<<2];
LL g[N][19];
LL solve(int x,int k){
if(L[x]<=k)return x-k;
LL ret=x-L[x];x=L[x];
int s=1;
for(int i=18;~i;--i){
if(f[x][i]>=k){
ret+=(LL)s*(x-f[x][i])+g[x][i];
s+=1<<i;
x=f[x][i];
}
}
return ret+(s+1)*(x-k);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=2;i<=n;++i)
cin>>L[i];
mn[n]=L[n];
for(int i=n-1;i;--i)
mn[i]=min(L[i],mn[i+1]);
for(int i=1;i<=n;++i)
f[i][0]=mn[i],g[i][0]=i-mn[i];
for(int j=1;j<19;++j)
for(int i=1;i<=n;++i){
f[i][j]=f[f[i][j-1]][j-1];
g[i][j]=g[i][j-1]+g[f[i][j-1]][j-1]+(1LL<<j-1)*(f[i][j-1]-f[i][j]);
}
cin>>q;
while(q--){
int l,r,x;
cin>>l>>r>>x;
LL A=solve(x,l)-solve(x,r+1),B=r-l+1,g=__gcd(A,B);
cout<<A/g<<'/'<<B/g<<'\n';
}
return 0;
}