蒟蒻zyd:这不是大水题吗?看我写个
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)的诡异ST表卡卡常数跑过去
题目:输出区间距离和
蒟蒻zyd:(笑容逐渐消失)
没想到一道倍增题能这么巧(毒)妙(瘤)……
一些奇奇怪怪的性质
这里是需要用到的性质……
为了方便,这里把“花费1单位时间进行传送”称为“走了1步”qwq
Ps.一些类似的情况就不画图了……我不会说其实是我懒QWQ
性质1
假设起始点是 s s s,如果当前走了 t t t步,能到达的最左端的点是 x x x,那么一定能在 t t t步内从 s s s到达 [ x , s − 1 ] [x,s-1] [x,s−1]内的任意一点。
证明:如果某个点
p
p
p(
x
≤
p
<
s
x\le p <s
x≤p<s)不能到达,因为某个点向左连接的点是连续的一段,所以
s
s
s能到的点中的
l
l
l的最小值一定
>
p
>p
>p(否则就珂以到
p
p
p了qwq),所以无法从
s
s
s能到的点中的任意一点走到
p
p
p左边,就矛盾了qwq
为什么能在
t
t
t步内到达呢?类似地,比如要
t
+
1
t+1
t+1步才能到
p
p
p,那么也不能在
t
t
t步内到达
p
p
p左边(不然从
t
t
t步内走到
p
p
p左边的点走到
p
p
p即可qwq),矛盾qwq
所以得证。
性质2
假设起点是
s
s
s,终点是
x
x
x,那么
s
s
s到
x
x
x的最短路只能是这两种之一:
1.一直向左走
2.一开始先从
s
s
s向右走一步,再一直向左走。
换言之,如果要向右走,只能一开始向右一步,其他时候就只能向左走了。
先证明不存在从
s
s
s向右走两步的情况:
设
s
s
s走两次到
x
x
x,走一次到
y
y
y。若存在
l
[
x
]
<
l
[
y
]
l[x]<l[y]
l[x]<l[y],即向右走两步比向右走一步更优的情况:
(如图所示,蓝边是
x
x
x到
l
[
x
]
l[x]
l[x]连的边)
由于一个点向左连的边的编号是连续的一段,所以
s
s
s一定有一条边连向
x
x
x,因此
s
s
s能一步就到
x
x
x,因此更优。
(如图所示,
s
s
s能通过红边一步到达
x
x
x)
若不存在
l
[
x
]
<
l
[
y
]
l[x]<l[y]
l[x]<l[y]的情况,那没必要走两步到
x
x
x了qwq,直接到
y
y
y然后向左走就珂以了qwq
再证明不能中途向右走的情况:
设起点
s
s
s向右走一步能到达的最右边的点是
r
(
x
)
r(x)
r(x),现在走到了
x
x
x,如果要向右走,那么到达的点也一定在
[
x
+
1
,
r
(
s
)
]
[x+1,r(s)]
[x+1,r(s)]范围内。
如果走到的点
>
r
(
x
)
>r(x)
>r(x),假设这个点为
y
y
y,那么
l
[
y
]
l[y]
l[y]一定比
s
s
s小,所以
s
s
s珂以向右走一步到达
y
y
y,但
y
>
r
(
x
)
y>r(x)
y>r(x),矛盾,所以
x
x
x向右走能到达的点在
[
x
+
1
,
r
(
s
)
]
[x+1,r(s)]
[x+1,r(s)]范围内qwq
根据性质1,向右走一步不会让答案更优,所以就不用往右走了qwq
(其实性质1只证明了
[
x
,
s
]
[x,s]
[x,s]范围内不会使答案更优,但是脑补一下也珂以明白在
[
s
+
1
,
r
(
s
)
]
[s+1,r(s)]
[s+1,r(s)]范围内也成立qwq)
综上,如果要向右走,那只能一开始向右走一步。
Ps. 有一个很显然的性质我感觉不用证明……就不把它列到上面正经证明的性质了awa
即:如果前
t
t
t步能到达的范围是
[
L
,
R
]
[L,R]
[L,R],那么第
t
+
1
t+1
t+1不能到达的范围是
m
i
n
{
l
[
i
]
}
,
L
<
=
i
<
=
R
min\{l[i]\},L<=i<=R
min{l[i]},L<=i<=R
题目解析
倍增:
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示从
i
i
i开始,先向右一次,再向左
2
j
2^j
2j次所能到达的最左端的点。
那么
f
[
i
]
[
j
]
=
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
f[i][j]=f[f[i][j-1]][j-1]
f[i][j]=f[f[i][j−1]][j−1]
因为由性质2,中途向右走不会使答案更优,所以从
f
[
i
]
[
j
−
1
]
f[i][j-1]
f[i][j−1]向左
2
j
−
1
2^{j-1}
2j−1步即为
f
[
i
]
[
j
]
f[i][j]
f[i][j]
显然向左走
x
+
1
x+1
x+1步会比向左
x
x
x步走得远,所以倍增数组是单调的。
查询就搞一个
s
u
m
[
i
]
[
j
]
sum[i][j]
sum[i][j],表示
i
i
i到
[
f
[
i
]
[
j
]
,
i
]
[f[i][j],i]
[f[i][j],i]的点的距离和qwq
然后大莉统计即珂(走出
2
i
2^i
2i步的贡献分两个部分统计)
具体见代码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;
}