蒟蒻zyd:這不是大水題嗎?看我寫個的詭異ST表卡卡常數跑過去
題目:輸出區間距離和
蒟蒻zyd:(笑容逐漸消失)
沒想到一道倍增題能這麼巧(毒)妙(瘤)……
一些奇奇怪怪的性質
這裏是需要用到的性質……
爲了方便,這裏把“花費1單位時間進行傳送”稱爲“走了1步”qwq
Ps.一些類似的情況就不畫圖了……我不會說其實是我懶QWQ
性質1
假設起始點是,如果當前走了步,能到達的最左端的點是,那麼一定能在步內從到達內的任意一點。
證明:如果某個點()不能到達,因爲某個點向左連接的點是連續的一段,所以能到的點中的的最小值一定(否則就珂以到了qwq),所以無法從能到的點中的任意一點走到左邊,就矛盾了qwq
爲什麼能在步內到達呢?類似地,比如要步才能到,那麼也不能在步內到達左邊(不然從步內走到左邊的點走到即可qwq),矛盾qwq
所以得證。
性質2
假設起點是,終點是,那麼到的最短路只能是這兩種之一:
1.一直向左走
2.一開始先從向右走一步,再一直向左走。
換言之,如果要向右走,只能一開始向右一步,其他時候就只能向左走了。
先證明不存在從向右走兩步的情況:
設走兩次到,走一次到。若存在,即向右走兩步比向右走一步更優的情況:
(如圖所示,藍邊是到連的邊)
由於一個點向左連的邊的編號是連續的一段,所以一定有一條邊連向,因此能一步就到,因此更優。
(如圖所示,能通過紅邊一步到達)
若不存在的情況,那沒必要走兩步到了qwq,直接到然後向左走就珂以了qwq
再證明不能中途向右走的情況:
設起點向右走一步能到達的最右邊的點是,現在走到了,如果要向右走,那麼到達的點也一定在範圍內。
如果走到的點,假設這個點爲,那麼一定比小,所以珂以向右走一步到達,但,矛盾,所以向右走能到達的點在範圍內qwq
根據性質1,向右走一步不會讓答案更優,所以就不用往右走了qwq
(其實性質1只證明了範圍內不會使答案更優,但是腦補一下也珂以明白在範圍內也成立qwq)
綜上,如果要向右走,那隻能一開始向右走一步。
Ps. 有一個很顯然的性質我感覺不用證明……就不把它列到上面正經證明的性質了awa
即:如果前步能到達的範圍是,那麼第不能到達的範圍是
題目解析
倍增:
表示從開始,先向右一次,再向左次所能到達的最左端的點。
那麼
因爲由性質2,中途向右走不會使答案更優,所以從向左步即爲
顯然向左走步會比向左步走得遠,所以倍增數組是單調的。
查詢就搞一個,表示到的點的距離和qwq
然後大莉統計即珂(走出步的貢獻分兩個部分統計)
具體見代碼qwq
毒瘤代碼
#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
inline void write(const int x) {
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int Size=300005;
const int INF=0x3f3f3f3f;
int n,l[Size],LOG[Size],f[Size][21],sum[Size][21];
int Calc(int x,int pos) {
if(l[pos]<=x) return pos-x;
int ans=pos-l[pos]; //先向左跳一次
pos=l[pos];
int cnt=1;
//跳到再跳一次就<x的位置
for(re i=LOG[pos]; i>=0; i--) {
if(f[pos][i]>x) {
//f[pos][i]~pos到pos的距離和+pos到原點的距離*這段區間的個數
ans+=sum[pos][i]+(pos-f[pos][i])*cnt;
pos=f[pos][i];
cnt+=1<<i;
}
}
return ans+(pos-x)*(cnt+1);
}
int main() {
n=read();
LOG[0]=-1;
LOG[1]=0;
for(re i=2; i<=n; i++) {
l[i]=read();
LOG[i]=LOG[i>>1]+1;
}
f[n+1][0]=INF;
for(re i=n; i; i--) {
f[i][0]=min(f[i+1][0],l[i]);
sum[i][0]=i-f[i][0]; //跳一步就珂以,所以距離和爲區間點數
}
for(re j=1; j<=18; j++) {
for(re i=1<<j; i<=n; i++) {
if(f[i][j-1]) {
f[i][j]=f[f[i][j-1]][j-1];
sum[i][j]=sum[i][j-1]+sum[f[i][j-1]][j-1]+((f[i][j-1]-f[i][j])<<(j-1));
}
}
}
int q=read();
while(q--) {
int l=read();
int r=read();
int x=read();
int p=Calc(l,x)-Calc(r+1,x);
int q=r-l+1;
int k=__gcd(p,q);
printf("%d/%d\n",p/k,q/k);
}
return 0;
}