給定 \(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; }