題意:n個點的圖,點i和[l[i],i)的全部點連雙向邊。每次詢問(l,r,x)表示x到[l,r]的全部點的最短路徑長度和。數組
首先這題顯然能夠線段樹優化建圖,可是須要比較好的常數才能經過45分,還須要發掘性質。優化
先不考慮往右走的狀況,對於一個點x,每一個點i與x的最短距離必定造成一個個連續區間,即:設f[i][j]表示i走j步能到的最左的點,則$f[i][j+1]=\min\limits_{k=f[i][j]}^{i-1}l[k]$。因此只要往前掃一遍就能求出f[i]數組。spa
接着考慮往右走的狀況,能夠證實,一個點最多隻須要往右走一次,因此只須要日後掃一遍就能求出新的f[i]數組。這樣咱們記錄一個前綴和就能夠在$O(n^2)$複雜度內解決問題。code
能夠發現f[i][j]這個數組顯然是能夠倍增優化的,直接套上RMQ相似的模板便可。blog
這裏有一個簡化代碼的方法,就是f[i][j]改成表示[i..n]的全部點走$2^j$步以後能到達的最靠前的點,這樣就能夠直接倍增轉移了。可是這樣就要判斷i最後是否須要先往右走一步,這裏又有一個小技巧:先強制往左走一步,剩下的直接處理便可。it
總碼長不到1k。io
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 typedef long long ll; 5 using namespace std; 6 7 const int N=300010; 8 int n,Q,l,r,x,L[N],to[20][N]; 9 ll sm[20][N]; 10 11 int gcd(int a,int b){ return b ? gcd(b,a%b) : a; } 12 13 ll calc(int l,int r){ 14 if (L[r]<=l) return r-l; 15 ll ans=r-L[r]; r=L[r]; int tot=1; 16 for (int i=19; ~i; i--) 17 if (to[i][r]>l) ans+=sm[i][r]+tot*(r-to[i][r]),r=to[i][r],tot+=1<<i; 18 return ans+(r-l)*(tot+1); 19 } 20 21 int main(){ 22 freopen("pkua.in","r",stdin); 23 freopen("pkua.out","w",stdout); 24 scanf("%d",&n); L[1]=1; 25 rep(i,2,n) scanf("%d",&L[i]); 26 to[0][n]=L[n]; sm[0][n]=n-L[n]; 27 for (int i=n-1; i; i--) to[0][i]=min(to[0][i+1],L[i]),sm[0][i]=i-to[0][i]; 28 rep(i,1,19) rep(j,1,n) if (to[i-1][j]){ 29 to[i][j]=to[i-1][to[i-1][j]]; 30 sm[i][j]=sm[i-1][j]+sm[i-1][to[i-1][j]]+(to[i-1][j]-to[i][j])*(1ll<<(i-1)); 31 } 32 for (scanf("%d",&Q); Q--; ){ 33 scanf("%d%d%d",&l,&r,&x); 34 ll a=calc(l,x)-calc(r+1,x),b=r-l+1; int d=gcd(a%b,b); 35 printf("%lld/%lld\n",a/d,b/d); 36 } 37 return 0; 38 }