给定 \(n\) 个点, 每一个点与 \([l_i,i-1]\) 之间的点创建有单位距离的双向边. \(q\) 组询问从 \(x\) 走到 \([l,r]\) 中的随机一点的指望距离. 输出既约分数.c++
\(n,q\le 3\times 10^5\), \(l<r<x\).git
显然对于一个 \(k\), \(k\) 步以内能到达的点是 \([1,x)\) 的一个后缀. 那么也就是说 \([1,x)\) 中的点的答案被分红了若干段, 每段的答案相同且向前递增.spa
手动模拟一下, 咱们发现第一步能够走到的左端点是 \(l_x\), 第二步能够走到的就是 \(\min\limits_{k\ge l_x}l_k\) 了. 缘由是全部可能一步到达 \(\min\limits_{k\ge l_x}l_k\) 的点都必然能够被 \(x\) 一步到达(若是 \(l_k\) 在 \(k>x\) 处取得最小值, 那么显然 \(l_k<x\). 又因为 \(k\) 链接的是 \([l_k,k)\), 那么必然和 \(x\) 直接相连). 且后面的步骤都是形如 \(l_k\) 的后缀 \(\min\) 的形式.3d
因而咱们能够在第二步及之后尝试倍增.code
倍增的同时不只记录到达的左端点, 同时记录一下倍增时跳过的点的距离总和. 这样就能够在 \(O(\log n)\) 的时间内计算出 \(x\) 到 \([l,x)\) 内的全部点的最短路之和了. 两个后缀和相减便可获得 \([l,r]\) 内的答案.blog
最后约分一下输出就完了. 虽然最终答案是 \(O(n^2)\) 级别的可是数据比较弱并无爆 int
...get
#include <bits/stdc++.h> const int MAXN=3e5+10; int n; int q; int l[MAXN]; int lg[MAXN]; int sum[20][MAXN]; int prev[20][MAXN]; int* minl=prev[0]; int ReadInt(); int Calc(int,int); int main(){ n=ReadInt(); for(int i=2;i<=n;i++) l[i]=ReadInt(); q=ReadInt(); l[1]=1; minl[n+1]=n; for(int i=n;i>=1;i--){ minl[i]=std::min(l[i],minl[i+1]); sum[0][i]=i-minl[i]; } for(int i=1;i<=n;i++) sum[0][i]=i-minl[i]; for(int i=1;(1<<i)<=n;i++){ lg[1<<i]=1; for(int j=1;j<=n;j++){ prev[i][j]=prev[i-1][prev[i-1][j]]; sum[i][j]=sum[i-1][j]+sum[i-1][prev[i-1][j]]+((prev[i-1][j]-prev[i][j])<<(i-1)); } } for(int i=1;i<=n;i++) lg[i]+=lg[i-1]; for(int i=0;i<q;i++){ int l=ReadInt(),r=ReadInt(),pos=ReadInt(); int a=Calc(pos,l)-Calc(pos,r+1); int b=r-l+1; int gcd=std::__gcd(a,b); printf("%d/%d\n",a/gcd,b/gcd); } return 0; } int Calc(int pos,int lim){ if(l[pos]<lim) return pos-lim; int dis=0,p=l[pos],ans=pos-lim; // printf("x %d %d\n",ans,p); for(int i=lg[p];i>=0;i--){ if(lim<=prev[i][p]){ ans+=sum[i][p]+(p-prev[i][p])*dis; // printf("$ %d Q(%d,%d) %d\n",i,pos,lim,ans); p=prev[i][p]; dis|=(1<<i); } } return ans+(p-lim)*(dis+1); } inline int ReadInt(){ int x=0; register char ch=getchar(); while(!isdigit(ch)) ch=getchar(); while(isdigit(ch)){ x=x*10+ch-'0'; ch=getchar(); } return x; }